Implement notification ranking by topic.

Including sensitive notification visibility & DND bypass.

Bug: 22451710
Change-Id: I5f3a0c6d59de0495cb904bad6f886eb29ed4bda4
This commit is contained in:
Julia Reynolds
2015-10-19 13:51:23 -04:00
parent 3f72604be8
commit 233a5f97ea
15 changed files with 221 additions and 76 deletions

View File

@@ -4867,6 +4867,7 @@ package android.app {
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff

View File

@@ -4985,6 +4985,7 @@ package android.app {
field public static final int PRIORITY_MAX = 2; // 0x2
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
field public static final int VISIBILITY_PRIVATE = 0; // 0x0
field public static final int VISIBILITY_PUBLIC = 1; // 0x1
field public static final int VISIBILITY_SECRET = -1; // 0xffffffff

View File

@@ -47,11 +47,11 @@ interface INotificationManager
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
void setPackagePriority(String pkg, int uid, int priority);
int getPackagePriority(String pkg, int uid);
void setPackageVisibilityOverride(String pkg, int uid, int visibility);
int getPackageVisibilityOverride(String pkg, int uid);
ParceledListSlice getTopics(String pkg, int uid);
void setTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
int getTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic);
void setTopicPriority(String pkg, int uid, in Notification.Topic topic, int priority);
int getTopicPriority(String pkg, int uid, in Notification.Topic topic);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.

View File

@@ -17,3 +17,4 @@
package android.app;
parcelable Notification;
parcelable Notification.Topic;

View File

@@ -21,6 +21,7 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -1433,6 +1434,9 @@ public class Notification implements Parcelable
};
}
@SystemApi
public static final String TOPIC_DEFAULT = "system_default_topic";
private Topic topic;
public Topic getTopic() {
@@ -3419,6 +3423,10 @@ public class Notification implements Parcelable
mN.extras.putStringArray(EXTRA_PEOPLE,
mPersonList.toArray(new String[mPersonList.size()]));
}
if (mN.topic == null) {
mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
R.string.default_notification_topic_label));
}
return mN;
}

View File

@@ -33,6 +33,7 @@ public class MetricsLogger implements MetricsConstants {
public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
public static final int ACTION_ZEN_ALLOW_PEEK = 261;
public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {

View File

@@ -1946,9 +1946,9 @@
See {@link com.android.server.notification.NotificationSignalExtractor} -->
<string-array name="config_notificationSignalExtractors">
<item>com.android.server.notification.ValidateNotificationPeople</item>
<item>com.android.server.notification.PackagePriorityExtractor</item>
<item>com.android.server.notification.TopicPriorityExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
<item>com.android.server.notification.PackageVisibilityExtractor</item>
<item>com.android.server.notification.TopicVisibilityExtractor</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default

View File

@@ -4072,4 +4072,6 @@
<item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
<item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
</plurals>
<string name="default_notification_topic_label">Miscellaneous</string>
</resources>

View File

@@ -2337,4 +2337,5 @@
<java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
<java-symbol type="string" name="config_packagedKeyboardName" />
<java-symbol type="string" name="default_notification_topic_label" />
</resources>

View File

@@ -1211,29 +1211,36 @@ public class NotificationManagerService extends SystemService {
}
@Override
public void setPackagePriority(String pkg, int uid, int priority) {
public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
checkCallerIsSystem();
mRankingHelper.setPackagePriority(pkg, uid, priority);
return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
}
@Override
public void setTopicPriority(String pkg, int uid, Notification.Topic topic, int priority) {
checkCallerIsSystem();
mRankingHelper.setTopicPriority(pkg, uid, topic, priority);
savePolicyFile();
}
@Override
public int getPackagePriority(String pkg, int uid) {
public int getTopicPriority(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
return mRankingHelper.getPackagePriority(pkg, uid);
return mRankingHelper.getTopicPriority(pkg, uid, topic);
}
@Override
public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
public void setTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic,
int visibility) {
checkCallerIsSystem();
mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
mRankingHelper.setTopicVisibilityOverride(pkg, uid, topic, visibility);
savePolicyFile();
}
@Override
public int getPackageVisibilityOverride(String pkg, int uid) {
public int getTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic) {
checkCallerIsSystem();
return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
return mRankingHelper.getTopicVisibilityOverride(pkg, uid, topic);
}
/**

View File

@@ -15,12 +15,20 @@
*/
package com.android.server.notification;
import android.app.Notification;
import java.util.List;
public interface RankingConfig {
int getPackagePriority(String packageName, int uid);
void setPackagePriority(String packageName, int uid, int priority);
List<Notification.Topic> getTopics(String packageName, int uid);
int getPackageVisibilityOverride(String packageName, int uid);
int getTopicPriority(String packageName, int uid, Notification.Topic topic);
void setPackageVisibilityOverride(String packageName, int uid, int visibility);
void setTopicPriority(String packageName, int uid, Notification.Topic topic, int priority);
int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic);
void setTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic,
int visibility);
}

View File

@@ -27,6 +27,8 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -35,6 +37,8 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
@@ -45,11 +49,14 @@ public class RankingHelper implements RankingConfig {
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String ATT_VERSION = "version";
private static final String TAG_TOPIC = "topic";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_TOPIC_ID = "id";
private static final String ATT_TOPIC_LABEL = "label";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY =
@@ -161,12 +168,14 @@ public class RankingHelper implements RankingConfig {
} else {
r = getOrCreateRecord(name, uid);
}
if (priority != DEFAULT_PRIORITY) {
r.priority = priority;
}
if (vis != DEFAULT_VISIBILITY) {
r.visibility = vis;
}
// Migrate package level settings to the default topic.
// Might be overwritten by parseTopics.
Topic defaultTopic = r.topics.get(Notification.TOPIC_DEFAULT);
defaultTopic.priority = priority;
defaultTopic.visibility = vis;
parseTopics(r, parser);
}
}
}
@@ -174,6 +183,38 @@ public class RankingHelper implements RankingConfig {
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
public void parseTopics(Record r, XmlPullParser parser)
throws XmlPullParserException, IOException {
final int innerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (TAG_TOPIC.equals(tagName)) {
int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
String id = parser.getAttributeValue(null, ATT_TOPIC_ID);
CharSequence label = parser.getAttributeValue(null, ATT_TOPIC_LABEL);
if (!TextUtils.isEmpty(id)) {
Topic topic = new Topic(new Notification.Topic(id, label));
if (priority != DEFAULT_PRIORITY) {
topic.priority = priority;
}
if (vis != DEFAULT_VISIBILITY) {
topic.visibility = vis;
}
r.topics.put(id, topic);
}
}
}
}
private static String recordKey(String pkg, int uid) {
return pkg + "|" + uid;
}
@@ -185,21 +226,14 @@ public class RankingHelper implements RankingConfig {
r = new Record();
r.pkg = pkg;
r.uid = uid;
r.topics.put(Notification.TOPIC_DEFAULT,
new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
mContext.getString(R.string.default_notification_topic_label))));
mRecords.put(key, r);
}
return r;
}
private void removeDefaultRecords() {
final int N = mRecords.size();
for (int i = N - 1; i >= 0; i--) {
final Record r = mRecords.valueAt(i);
if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) {
mRecords.remove(i);
}
}
}
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -213,20 +247,32 @@ public class RankingHelper implements RankingConfig {
}
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
if (r.priority != DEFAULT_PRIORITY) {
out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
}
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
writeTopicsXml(out, r);
out.endTag(null, TAG_PACKAGE);
}
out.endTag(null, TAG_RANKING);
}
public void writeTopicsXml(XmlSerializer out, Record r) throws IOException {
for (Topic t : r.topics.values()) {
out.startTag(null, TAG_TOPIC);
out.attribute(null, ATT_TOPIC_ID, t.topic.getId());
out.attribute(null, ATT_TOPIC_LABEL, t.topic.getLabel().toString());
if (t.priority != DEFAULT_PRIORITY) {
out.attribute(null, ATT_PRIORITY, Integer.toString(t.priority));
}
if (t.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(t.visibility));
}
out.endTag(null, TAG_TOPIC);
}
}
private void updateConfig() {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
@@ -322,37 +368,54 @@ public class RankingHelper implements RankingConfig {
}
@Override
public int getPackagePriority(String packageName, int uid) {
final Record r = mRecords.get(recordKey(packageName, uid));
return r != null ? r.priority : DEFAULT_PRIORITY;
public List<Notification.Topic> getTopics(String packageName, int uid) {
final Record r = getOrCreateRecord(packageName, uid);
List<Notification.Topic> topics = new ArrayList<>();
for (Topic t : r.topics.values()) {
topics.add(t.topic);
}
return topics;
}
@Override
public void setPackagePriority(String packageName, int uid, int priority) {
if (priority == getPackagePriority(packageName, uid)) {
return;
}
getOrCreateRecord(packageName, uid).priority = priority;
removeDefaultRecords();
public int getTopicPriority(String packageName, int uid, Notification.Topic topic) {
final Record r = getOrCreateRecord(packageName, uid);
return getOrCreateTopic(r, topic).priority;
}
@Override
public void setTopicPriority(String packageName, int uid, Notification.Topic topic,
int priority) {
final Record r = getOrCreateRecord(packageName, uid);
getOrCreateTopic(r, topic).priority = priority;
updateConfig();
}
@Override
public int getPackageVisibilityOverride(String packageName, int uid) {
final Record r = mRecords.get(recordKey(packageName, uid));
return r != null ? r.visibility : DEFAULT_VISIBILITY;
public int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic) {
final Record r = getOrCreateRecord(packageName, uid);
return getOrCreateTopic(r, topic).visibility;
}
@Override
public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
if (visibility == getPackageVisibilityOverride(packageName, uid)) {
return;
}
getOrCreateRecord(packageName, uid).visibility = visibility;
removeDefaultRecords();
public void setTopicVisibilityOverride(String pkgName, int uid, Notification.Topic topic,
int visibility) {
final Record r = getOrCreateRecord(pkgName, uid);
getOrCreateTopic(r, topic).visibility = visibility;
updateConfig();
}
private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
Topic t = r.topics.get(topic.getId());
if (t != null) {
return t;
} else {
t = new Topic(topic);
r.topics.put(topic.getId(), t);
return t;
}
}
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;
@@ -385,15 +448,22 @@ public class RankingHelper implements RankingConfig {
pw.print(" (");
pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
pw.print(')');
if (r.priority != DEFAULT_PRIORITY) {
pw.print(" priority=");
pw.print(Notification.priorityToString(r.priority));
}
if (r.visibility != DEFAULT_VISIBILITY) {
pw.print(" visibility=");
pw.print(Notification.visibilityToString(r.visibility));
}
pw.println();
for (Topic t : r.topics.values()) {
pw.print(prefix);
pw.print(" ");
pw.print(" ");
pw.print(t.topic.getId());
if (t.priority != DEFAULT_PRIORITY) {
pw.print(" priority=");
pw.print(Notification.priorityToString(t.priority));
}
if (t.visibility != DEFAULT_VISIBILITY) {
pw.print(" visibility=");
pw.print(Notification.visibilityToString(t.visibility));
}
pw.println();
}
}
}
}
@@ -429,8 +499,16 @@ public class RankingHelper implements RankingConfig {
String pkg;
int uid = UNKNOWN_UID;
Map<String, Topic> topics = new ArrayMap<>();
}
private static class Topic {
Notification.Topic topic;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
}
public Topic(Notification.Topic topic) {
this.topic = topic;
}
}
}

View File

@@ -18,8 +18,11 @@ package com.android.server.notification;
import android.content.Context;
import android.util.Slog;
public class PackagePriorityExtractor implements NotificationSignalExtractor {
private static final String TAG = "ImportantPackageExtractor";
/**
* Determines if the given notification can bypass Do Not Disturb.
*/
public class TopicPriorityExtractor implements NotificationSignalExtractor {
private static final String TAG = "ImportantTopicExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,8 @@ public class PackagePriorityExtractor implements NotificationSignalExtractor {
return null;
}
final int packagePriority = mConfig.getPackagePriority(
record.sbn.getPackageName(), record.sbn.getUid());
final int packagePriority = mConfig.getTopicPriority(record.sbn.getPackageName(),
record.sbn.getUid(), record.sbn.getNotification().getTopic());
record.setPackagePriority(packagePriority);
return null;

View File

@@ -18,8 +18,11 @@ package com.android.server.notification;
import android.content.Context;
import android.util.Slog;
public class PackageVisibilityExtractor implements NotificationSignalExtractor {
private static final String TAG = "PackageVisibilityExtractor";
/**
* Determines if the given notification can display sensitive content on the lockscreen.
*/
public class TopicVisibilityExtractor implements NotificationSignalExtractor {
private static final String TAG = "TopicVisibilityExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
@@ -39,8 +42,9 @@ public class PackageVisibilityExtractor implements NotificationSignalExtractor {
return null;
}
final int packageVisibility = mConfig.getPackageVisibilityOverride(
record.sbn.getPackageName(), record.sbn.getUid());
final int packageVisibility = mConfig.getTopicVisibilityOverride(
record.sbn.getPackageName(), record.sbn.getUid(),
record.sbn.getNotification().getTopic());
record.setPackageVisibilityOverride(packageVisibility);
return null;

View File

@@ -160,6 +160,36 @@ public class NotificationTestList extends TestActivity
}
},
new Test("with topic Hello") {
public void run() {
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon1)
.setWhen(mActivityCreateTime)
.setContentTitle("hihi")
.setContentText("This is a notification!!!")
.setContentIntent(makeIntent2())
.setTopic(new Notification.Topic("hello", "Hello"))
.build();
mNM.notify(999, n);
}
},
new Test("with topic GoodBye") {
public void run() {
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon1)
.setWhen(mActivityCreateTime)
.setContentTitle("byebye")
.setContentText("This is a notification!!!")
.setContentIntent(makeIntent2())
.setTopic(new Notification.Topic("bye", "Goodbye"))
.build();
mNM.notify(9999, n);
}
},
new Test("Whens") {
public void run()
{