Merge "Allow the ranker to autobundle notifications." into nyc-dev

This commit is contained in:
Julia Reynolds
2016-04-08 20:13:44 +00:00
committed by Android (Google) Code Review
18 changed files with 889 additions and 118 deletions

View File

@@ -34741,6 +34741,7 @@ package android.service.notification {
method public int getImportance();
method public java.lang.CharSequence getImportanceExplanation();
method public java.lang.String getKey();
method public java.lang.String getOverrideGroupKey();
method public int getRank();
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
@@ -34771,13 +34772,16 @@ package android.service.notification {
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
method public java.lang.String getTag();
method public android.os.UserHandle getUser();
method public deprecated int getUserId();
method public boolean isClearable();
method public boolean isGroup();
method public boolean isOngoing();
method public void setOverrideGroupKey(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
}

View File

@@ -5076,6 +5076,7 @@ package android.app {
field public static final java.lang.String EXTRA_THREAD_TITLE = "android.threadTitle";
field public static final java.lang.String EXTRA_TITLE = "android.title";
field public static final java.lang.String EXTRA_TITLE_BIG = "android.title.big";
field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400
field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
@@ -37119,6 +37120,22 @@ package android.service.media {
package android.service.notification {
public final class Adjustment implements android.os.Parcelable {
ctor public Adjustment(java.lang.String, java.lang.String, int, android.os.Bundle, java.lang.CharSequence, android.net.Uri);
ctor protected Adjustment(android.os.Parcel);
method public int describeContents();
method public java.lang.CharSequence getExplanation();
method public int getImportance();
method public java.lang.String getKey();
method public java.lang.String getPackage();
method public android.net.Uri getReference();
method public android.os.Bundle getSignals();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final java.lang.String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
field public static final java.lang.String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";
}
public class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, java.lang.String, int);
ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
@@ -37212,6 +37229,7 @@ package android.service.notification {
method public int getImportance();
method public java.lang.CharSequence getImportanceExplanation();
method public java.lang.String getKey();
method public java.lang.String getOverrideGroupKey();
method public int getRank();
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
@@ -37235,11 +37253,12 @@ package android.service.notification {
public abstract class NotificationRankerService extends android.service.notification.NotificationListenerService {
ctor public NotificationRankerService();
method public final void adjustImportance(java.lang.String, android.service.notification.NotificationRankerService.Adjustment);
method public final void adjustNotification(android.service.notification.Adjustment);
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
method public final android.os.IBinder onBind(android.content.Intent);
method public void onNotificationActionClick(java.lang.String, long, int);
method public void onNotificationClick(java.lang.String, long);
method public abstract android.service.notification.NotificationRankerService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
method public void onNotificationRemoved(java.lang.String, long, int);
method public void onNotificationVisibilityChanged(java.lang.String, long, boolean);
field public static final int REASON_APP_CANCEL = 8; // 0x8
@@ -37256,14 +37275,11 @@ package android.service.notification {
field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationRankerService";
}
public class NotificationRankerService.Adjustment {
ctor public NotificationRankerService.Adjustment(int, java.lang.CharSequence, android.net.Uri);
}
public class StatusBarNotification implements android.os.Parcelable {
ctor public StatusBarNotification(java.lang.String, java.lang.String, int, java.lang.String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
@@ -37273,13 +37289,16 @@ package android.service.notification {
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
method public java.lang.String getTag();
method public android.os.UserHandle getUser();
method public deprecated int getUserId();
method public boolean isClearable();
method public boolean isGroup();
method public boolean isOngoing();
method public void setOverrideGroupKey(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
}

View File

@@ -34814,6 +34814,7 @@ package android.service.notification {
method public int getImportance();
method public java.lang.CharSequence getImportanceExplanation();
method public java.lang.String getKey();
method public java.lang.String getOverrideGroupKey();
method public int getRank();
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
@@ -34844,13 +34845,16 @@ package android.service.notification {
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
method public java.lang.String getTag();
method public android.os.UserHandle getUser();
method public deprecated int getUserId();
method public boolean isClearable();
method public boolean isGroup();
method public boolean isOngoing();
method public void setOverrideGroupKey(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
}

View File

@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.net.Uri;
import android.os.Bundle;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
@@ -80,7 +81,8 @@ interface INotificationManager
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
void setInterruptionFilter(String pkg, int interruptionFilter);
void setImportanceFromRankerService(in INotificationListener token, String key, int importance, CharSequence explanation);
void applyAdjustmentFromRankerService(in INotificationListener token, in Adjustment adjustment);
void applyAdjustmentsFromRankerService(in INotificationListener token, in List<Adjustment> adjustments);
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);

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;
@@ -496,6 +497,15 @@ public class Notification implements Parcelable
*/
public static final int FLAG_GROUP_SUMMARY = 0x00000200;
/**
* Bit to be bitswise-ored into the {@link #flags} field that should be
* set if this notification is the group summary for an auto-group of notifications.
*
* @hide
*/
@SystemApi
public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400;
public int flags;
/** @hide */
@@ -1945,13 +1955,9 @@ public class Notification implements Parcelable
* @hide
*/
public static void addFieldsFromContext(Context context, Notification notification) {
if (notification.extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO) == null) {
notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO,
context.getApplicationInfo());
}
if (!notification.extras.containsKey(EXTRA_ORIGINATING_USERID)) {
notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId());
}
notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO,
context.getApplicationInfo());
notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId());
}
@Override
@@ -3020,12 +3026,13 @@ public class Notification implements Parcelable
/**
* @hide
*/
public void setFlag(int mask, boolean value) {
public Builder setFlag(int mask, boolean value) {
if (value) {
mN.flags |= mask;
} else {
mN.flags &= ~mask;
}
return this;
}
/**

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2016, 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;
parcelable Adjustment;

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2016 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 android.annotation.SystemApi;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Ranking updates from the Ranker.
*
* @hide
*/
@SystemApi
public final class Adjustment implements Parcelable {
private final String mPackage;
private final String mKey;
private final int mImportance;
private final CharSequence mExplanation;
private final Uri mReference;
private final Bundle mSignals;
public static final String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
public static final String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";
/**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
* @param key The notification key.
* @param importance The recommended importance of the notification.
* @param signals A bundle of signals that should inform notification grouping and ordering.
* @param explanation A human-readable justification for the adjustment.
* @param reference A reference to an external object that augments the
* explanation, such as a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI},
* or null.
*/
public Adjustment(String pkg, String key, int importance, Bundle signals,
CharSequence explanation, Uri reference) {
mPackage = pkg;
mKey = key;
mImportance = importance;
mSignals = signals;
mExplanation = explanation;
mReference = reference;
}
protected Adjustment(Parcel in) {
if (in.readInt() == 1) {
mPackage = in.readString();
} else {
mPackage = null;
}
if (in.readInt() == 1) {
mKey = in.readString();
} else {
mKey = null;
}
mImportance = in.readInt();
if (in.readInt() == 1) {
mExplanation = in.readCharSequence();
} else {
mExplanation = null;
}
mReference = in.readParcelable(Uri.class.getClassLoader());
mSignals = in.readBundle();
}
public static final Creator<Adjustment> CREATOR = new Creator<Adjustment>() {
@Override
public Adjustment createFromParcel(Parcel in) {
return new Adjustment(in);
}
@Override
public Adjustment[] newArray(int size) {
return new Adjustment[size];
}
};
public String getPackage() {
return mPackage;
}
public String getKey() {
return mKey;
}
public int getImportance() {
return mImportance;
}
public CharSequence getExplanation() {
return mExplanation;
}
public Uri getReference() {
return mReference;
}
public Bundle getSignals() {
return mSignals;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mPackage != null) {
dest.writeInt(1);
dest.writeString(mPackage);
} else {
dest.writeInt(0);
}
if (mKey != null) {
dest.writeInt(1);
dest.writeString(mKey);
} else {
dest.writeInt(0);
}
dest.writeInt(mImportance);
if (mExplanation != null) {
dest.writeInt(1);
dest.writeCharSequence(mExplanation);
} else {
dest.writeInt(0);
}
dest.writeParcelable(mReference, flags);
dest.writeBundle(mSignals);
}
}

View File

@@ -1052,6 +1052,8 @@ public abstract class NotificationListenerService extends Service {
private int mSuppressedVisualEffects;
private @Importance int mImportance;
private CharSequence mImportanceExplanation;
// System specified group key.
private String mOverrideGroupKey;
public Ranking() {}
@@ -1130,9 +1132,17 @@ public abstract class NotificationListenerService extends Service {
return mImportanceExplanation;
}
/**
* If the system has overriden the group key, then this will be non-null, and this
* key should be used to bundle notifications.
*/
public String getOverrideGroupKey() {
return mOverrideGroupKey;
}
private void populate(String key, int rank, boolean matchesInterruptionFilter,
int visibilityOverride, int suppressedVisualEffects, int importance,
CharSequence explanation) {
CharSequence explanation, String overrideGroupKey) {
mKey = key;
mRank = rank;
mIsAmbient = importance < IMPORTANCE_LOW;
@@ -1141,6 +1151,7 @@ public abstract class NotificationListenerService extends Service {
mSuppressedVisualEffects = suppressedVisualEffects;
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKey = overrideGroupKey;
}
/**
@@ -1184,6 +1195,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayMap<String, Integer> mSuppressedVisualEffects;
private ArrayMap<String, Integer> mImportance;
private ArrayMap<String, String> mImportanceExplanation;
private ArrayMap<String, String> mOverrideGroupKeys;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1210,7 +1222,7 @@ public abstract class NotificationListenerService extends Service {
int rank = getRank(key);
outRanking.populate(key, rank, !isIntercepted(key),
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key));
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key));
return rank >= 0;
}
@@ -1281,6 +1293,15 @@ public abstract class NotificationListenerService extends Service {
return mImportanceExplanation.get(key);
}
private String getOverrideGroupKey(String key) {
synchronized (this) {
if (mOverrideGroupKeys == null) {
buildOverrideGroupKeys();
}
}
return mOverrideGroupKeys.get(key);
}
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1335,6 +1356,15 @@ public abstract class NotificationListenerService extends Service {
}
}
// Locked by 'this'
private void buildOverrideGroupKeys() {
Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys();
mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size());
for (String key: overrideGroupKeys.keySet()) {
mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key));
}
}
// ----------- Parcelable
@Override

View File

@@ -22,14 +22,19 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.SomeArgs;
import java.util.List;
/**
* A service that helps the user manage notifications. This class is only used to
* extend the framework service and may not be implemented by non-framework components.
@@ -91,27 +96,8 @@ public abstract class NotificationRankerService extends NotificationListenerServ
/** Notification was canceled by the owning managed profile being turned off. */
public static final int REASON_PROFILE_TURNED_OFF = 15;
public class Adjustment {
int mImportance;
CharSequence mExplanation;
Uri mReference;
/**
* Create a notification importance adjustment.
*
* @param importance The final importance of the notification.
* @param explanation A human-readable justification for the adjustment.
* @param reference A reference to an external object that augments the
* explanation, such as a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI},
* or null.
*/
public Adjustment(int importance, CharSequence explanation, Uri reference) {
mImportance = importance;
mExplanation = explanation;
mReference = reference;
}
}
/** Autobundled summary notification was canceled because its group was unbundled */
public static final int REASON_UNAUTOBUNDLED = 16;
private Handler mHandler;
@@ -200,18 +186,32 @@ public abstract class NotificationRankerService extends NotificationListenerServ
}
/**
* Change the importance of an existing notification. N.B. this wont cause
* Updates a notification. N.B. this wont cause
* an existing notification to alert, but might allow a future update to
* this notification to alert.
*
* @param key the notification key
* @param adjustment the new importance with an explanation
* @param adjustment the adjustment with an explanation
*/
public final void adjustImportance(String key, Adjustment adjustment) {
public final void adjustNotification(Adjustment adjustment) {
if (!isBound()) return;
try {
getNotificationInterface().setImportanceFromRankerService(mWrapper, key,
adjustment.mImportance, adjustment.mExplanation);
getNotificationInterface().applyAdjustmentFromRankerService(mWrapper, adjustment);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
}
/**
* Updates existing notifications. Re-ranking won't occur until all adjustments are applied.
* N.B. this wont cause an existing notification to alert, but might allow a future update to
* these notifications to alert.
*
* @param adjustments a list of adjustments with explanations
*/
public final void adjustNotifications(List<Adjustment> adjustments) {
if (!isBound()) return;
try {
getNotificationInterface().applyAdjustmentsFromRankerService(mWrapper, adjustments);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
@@ -299,7 +299,7 @@ public abstract class NotificationRankerService extends NotificationListenerServ
args.recycle();
Adjustment adjustment = onNotificationEnqueued(sbn, importance, user);
if (adjustment != null) {
adjustImportance(sbn.getKey(), adjustment);
adjustNotification(adjustment);
}
} break;

View File

@@ -30,16 +30,18 @@ public class NotificationRankingUpdate implements Parcelable {
private final Bundle mSuppressedVisualEffects;
private final int[] mImportance;
private final Bundle mImportanceExplanation;
private final Bundle mOverrideGroupKeys;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation) {
int[] importance, Bundle explanation, Bundle overrideGroupKeys) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
mSuppressedVisualEffects = suppressedVisualEffects;
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKeys = overrideGroupKeys;
}
public NotificationRankingUpdate(Parcel in) {
@@ -50,6 +52,7 @@ public class NotificationRankingUpdate implements Parcelable {
mImportance = new int[mKeys.length];
in.readIntArray(mImportance);
mImportanceExplanation = in.readBundle();
mOverrideGroupKeys = in.readBundle();
}
@Override
@@ -65,6 +68,7 @@ public class NotificationRankingUpdate implements Parcelable {
out.writeBundle(mSuppressedVisualEffects);
out.writeIntArray(mImportance);
out.writeBundle(mImportanceExplanation);
out.writeBundle(mOverrideGroupKeys);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -101,4 +105,8 @@ public class NotificationRankingUpdate implements Parcelable {
public Bundle getImportanceExplanation() {
return mImportanceExplanation;
}
public Bundle getOverrideGroupKeys() {
return mOverrideGroupKeys;
}
}

View File

@@ -33,7 +33,8 @@ public class StatusBarNotification implements Parcelable {
private final int id;
private final String tag;
private final String key;
private final String groupKey;
private String groupKey;
private String overrideGroupKey;
private final int uid;
private final String opPkg;
@@ -51,6 +52,27 @@ public class StatusBarNotification implements Parcelable {
System.currentTimeMillis());
}
/** @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();
}
public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid,
int initialPid, int score, Notification notification, UserHandle user,
long postTime) {
@@ -84,15 +106,27 @@ public class StatusBarNotification implements Parcelable {
this.notification = new Notification(in);
this.user = UserHandle.readFromParcel(in);
this.postTime = in.readLong();
if (in.readInt() != 0) {
this.overrideGroupKey = in.readString();
} else {
this.overrideGroupKey = null;
}
this.key = key();
this.groupKey = groupKey();
}
private String key() {
return user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
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) {
@@ -105,6 +139,17 @@ public class StatusBarNotification implements Parcelable {
: "g:" + group);
}
/**
* Returns true if this notification is part of a group.
*/
public boolean isGroup() {
if (overrideGroupKey != null || 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);
@@ -121,6 +166,12 @@ public class StatusBarNotification implements Parcelable {
user.writeToParcel(out, flags);
out.writeLong(this.postTime);
if (this.overrideGroupKey != null) {
out.writeInt(1);
out.writeString(this.overrideGroupKey);
} else {
out.writeInt(0);
}
}
public int describeContents() {
@@ -149,22 +200,22 @@ public class StatusBarNotification implements Parcelable {
this.notification.cloneInto(no, false); // light copy
return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
0, no, this.user, this.postTime);
no, this.user, this.overrideGroupKey, this.postTime);
}
@Override
public StatusBarNotification clone() {
return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
0, this.notification.clone(), this.user, this.postTime);
this.notification.clone(), this.user, this.overrideGroupKey, this.postTime);
}
@Override
public String toString() {
return String.format(
"StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)",
"StatusBarNotification(pkg=%s user=%s id=%d tag=%s key=%s: %s)",
this.pkg, this.user, this.id, this.tag,
0, this.key, this.notification);
this.key, this.notification);
}
/** Convenience method to check the notification's flags for
@@ -257,6 +308,21 @@ public class StatusBarNotification implements Parcelable {
return groupKey;
}
/**
* 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
*/

View File

@@ -17,4 +17,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Android Services Library</string>
<string name="notification_ranker">Android Notification Ranking Service</string>
<string name="notification_ranker_autobundle_explanation">Auto-grouping updated by Ranking Service</string>
</resources>

View File

@@ -16,16 +16,36 @@
package android.ext.services.notification;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
import android.os.Bundle;
import android.service.notification.Adjustment;
import android.service.notification.NotificationRankerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import android.ext.services.R;
/**
* Class that provides an updatable ranker module for the notification manager..
*/
public final class Ranker extends NotificationRankerService {
private static final String TAG = "RocketRanker";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);;
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int AUTOBUNDLE_AT_COUNT = 4;
private static final String AUTOBUNDLE_KEY = "ranker_bundle";
// Map of package : notification keys. Only contains notifications that are not bundled
// by the app (aka no group or sort key).
Map<String, LinkedHashSet<String>> mUnbundledNotifications;
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
@@ -37,10 +57,146 @@ public final class Ranker extends NotificationRankerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
try {
List<String> notificationsToBundle = new ArrayList<>();
if (!sbn.isGroup()) {
// Not grouped by the app, add to the list of notifications for the app;
// send bundling update if app exceeds the autobundling limit.
synchronized (mUnbundledNotifications) {
LinkedHashSet<String> notificationsForPackage
= mUnbundledNotifications.get(sbn.getPackageName());
if (notificationsForPackage == null) {
notificationsForPackage = new LinkedHashSet<>();
}
if (notificationsForPackage.contains(sbn.getKey())) {
return;
}
notificationsForPackage.add(sbn.getKey());
mUnbundledNotifications.put(sbn.getPackageName(), notificationsForPackage);
if (notificationsForPackage.size() >= AUTOBUNDLE_AT_COUNT) {
// Autobundle all but the most recently posted (not updated) notification.
int count = 0;
for (String key : notificationsForPackage) {
if (count < notificationsForPackage.size() - 1) {
notificationsToBundle.add(key);
}
count++;
}
}
}
if (notificationsToBundle.size() > 0) {
adjustAutobundlingSummary(sbn.getPackageName(), notificationsToBundle.get(0),
true);
adjustNotificationBundling(sbn.getPackageName(), notificationsToBundle, true);
}
} else {
// Grouped, but not by us. Send updates to unautobundle, if we bundled it.
maybeUnbundle(sbn, false);
}
} catch (Exception e) {
Slog.e(TAG, "Failure processing new notification", e);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
try {
maybeUnbundle(sbn, true);
} catch (Exception e) {
Slog.e(TAG, "Error processing canceled notification", e);
}
}
/**
* Un-autobundles notifications that are now grouped by the app. Additionally cancels
* autobundling if the status change of this notification resulted in the loose notification
* count being under the limit.
*/
private void maybeUnbundle(StatusBarNotification sbn, boolean notificationGone) {
List<String> notificationsToUnAutobundle = new ArrayList<>();
boolean removeSummary = false;
synchronized (mUnbundledNotifications) {
LinkedHashSet<String> notificationsForPackage
= mUnbundledNotifications.get(sbn.getPackageName());
if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
return;
}
if (notificationsForPackage.remove(sbn.getKey())) {
if (!notificationGone) {
// Add the current notification to the unbundling list if it still exists.
notificationsToUnAutobundle.add(sbn.getKey());
}
// If the status change of this notification has brought the number of loose
// notifications back below the limit, remove the summary and un-autobundle.
if (notificationsForPackage.size() == AUTOBUNDLE_AT_COUNT - 1) {
removeSummary = true;
for (String key : notificationsForPackage) {
notificationsToUnAutobundle.add(key);
}
}
}
}
if (notificationsToUnAutobundle.size() > 0) {
if (removeSummary) {
adjustAutobundlingSummary(sbn.getPackageName(), null, false);
}
adjustNotificationBundling(sbn.getPackageName(), notificationsToUnAutobundle, false);
}
}
@Override
public void onListenerConnected() {
if (DEBUG) Log.i(TAG, "CONNECTED");
mUnbundledNotifications = new HashMap<>();
for (StatusBarNotification sbn : getActiveNotifications()) {
onNotificationPosted(sbn);
}
}
private void adjustAutobundlingSummary(String packageName, String key, boolean summaryNeeded) {
Bundle signals = new Bundle();
if (summaryNeeded) {
signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, true);
signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
} else {
signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false);
}
Adjustment adjustment = new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
getContext().getString(R.string.notification_ranker_autobundle_explanation), null);
if (DEBUG) {
Log.i(TAG, "Summary update for: " + packageName + " "
+ (summaryNeeded ? "adding" : "removing"));
}
try {
adjustNotification(adjustment);
} catch (Exception e) {
Slog.e(TAG, "Adjustment failed", e);
}
}
private void adjustNotificationBundling(String packageName, List<String> keys, boolean bundle) {
List<Adjustment> adjustments = new ArrayList<>();
for (String key : keys) {
adjustments.add(createBundlingAdjustment(packageName, key, bundle));
if (DEBUG) Log.i(TAG, "Sending bundling adjustment for: " + key);
}
try {
adjustNotifications(adjustments);
} catch (Exception e) {
Slog.e(TAG, "Adjustments failed", e);
}
}
private Adjustment createBundlingAdjustment(String packageName, String key, boolean bundle) {
Bundle signals = new Bundle();
if (bundle) {
signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
} else {
signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
}
return new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
getContext().getString(R.string.notification_ranker_autobundle_explanation), null);
}
}

View File

@@ -34,6 +34,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
/**
@@ -257,16 +258,21 @@ public class NotificationData {
}
public void add(Entry entry, RankingMap ranking) {
mEntries.put(entry.notification.getKey(), entry);
updateRankingAndSort(ranking);
synchronized (mEntries) {
mEntries.put(entry.notification.getKey(), entry);
}
mGroupManager.onEntryAdded(entry);
updateRankingAndSort(ranking);
}
public Entry remove(String key, RankingMap ranking) {
Entry removed = mEntries.remove(key);
Entry removed = null;
synchronized (mEntries) {
removed = mEntries.remove(key);
}
if (removed == null) return null;
updateRankingAndSort(ranking);
mGroupManager.onEntryRemoved(removed);
updateRankingAndSort(ranking);
return removed;
}
@@ -316,9 +322,30 @@ public class NotificationData {
return Ranking.IMPORTANCE_UNSPECIFIED;
}
public String getOverrideGroupKey(String key) {
if (mRankingMap != null) {
mRankingMap.getRanking(key, mTmpRanking);
return mTmpRanking.getOverrideGroupKey();
}
return null;
}
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);
final StatusBarNotification oldSbn = entry.notification.clone();
final String overrideGroupKey = getOverrideGroupKey(entry.key);
if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
//mGroupManager.onEntryBundlingUpdated(entry, getOverrideGroupKey(entry.key));
}
}
}
filterAndSort();
}
@@ -328,16 +355,18 @@ public class NotificationData {
public void filterAndSort() {
mSortedAndFiltered.clear();
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
StatusBarNotification sbn = entry.notification;
synchronized (mEntries) {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
StatusBarNotification sbn = entry.notification;
if (shouldFilterOut(sbn)) {
continue;
if (shouldFilterOut(sbn)) {
continue;
}
mSortedAndFiltered.add(entry);
}
mSortedAndFiltered.add(entry);
}
Collections.sort(mSortedAndFiltered, mRankingComparator);
@@ -398,16 +427,17 @@ public class NotificationData {
NotificationData.Entry e = mSortedAndFiltered.get(active);
dumpEntry(pw, indent, active, e);
}
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++;
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++;
}
}
}
}

View File

@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
/**
* A class to handle notifications and their corresponding groups.
@@ -121,6 +122,15 @@ public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChanged
}
}
public void onEntryBundlingUpdated(final NotificationData.Entry updated,
final String overrideGroupKey) {
final StatusBarNotification oldSbn = updated.notification.clone();
if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
updated.notification.setOverrideGroupKey(overrideGroupKey);
onEntryUpdated(updated, oldSbn);
}
}
private void updateSuppression(NotificationGroup group) {
if (group == null) {
return;
@@ -129,7 +139,7 @@ public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChanged
group.suppressed = group.summary != null && !group.expanded
&& (group.children.size() == 1
|| (group.children.size() == 0
&& !group.summary.notification.getNotification().isGroupChild()
&& group.summary.notification.getNotification().isGroupSummary()
&& hasIsolatedChildren(group)));
if (prevSuppressed != group.suppressed) {
mListener.onGroupsChanged();
@@ -173,7 +183,7 @@ public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChanged
public boolean isOnlyChildInSuppressedGroup(StatusBarNotification sbn) {
return isGroupSuppressed(sbn.getGroupKey())
&& sbn.getNotification().isGroupChild()
&& !sbn.getNotification().isGroupSummary()
&& getTotalNumberOfChildren(sbn) == 1;
}
@@ -278,11 +288,12 @@ public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChanged
}
return sbn.getNotification().isGroupSummary();
}
private boolean isGroupChild(StatusBarNotification sbn) {
if (isIsolated(sbn)) {
return false;
}
return sbn.getNotification().isGroupChild();
return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
}
private String getGroupKey(StatusBarNotification sbn) {
@@ -335,7 +346,7 @@ public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChanged
private boolean shouldIsolate(StatusBarNotification sbn) {
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
return sbn.getNotification().isGroupChild()
return (sbn.isGroup() && !sbn.getNotification().isGroupSummary())
&& (sbn.getNotification().fullScreenIntent != null
|| notificationGroup == null
|| !notificationGroup.expanded

View File

@@ -30,6 +30,7 @@ import static android.service.notification.NotificationRankerService.REASON_PACK
import static android.service.notification.NotificationRankerService.REASON_PACKAGE_CHANGED;
import static android.service.notification.NotificationRankerService.REASON_PACKAGE_SUSPENDED;
import static android.service.notification.NotificationRankerService.REASON_PROFILE_TURNED_OFF;
import static android.service.notification.NotificationRankerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationRankerService.REASON_USER_STOPPED;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
@@ -39,8 +40,6 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_NONE;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
import android.annotation.Nullable;
@@ -97,6 +96,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
@@ -136,7 +136,6 @@ import com.android.server.notification.ManagedServices.UserProfiles;
import libcore.io.IoUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
@@ -158,7 +157,6 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -263,6 +261,7 @@ public class NotificationManagerService extends SystemService {
new ArrayList<NotificationRecord>();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
new ArrayMap<String, NotificationRecord>();
final ArrayMap<String, String> mAutobundledSummaries = new ArrayMap<>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
final PolicyAccess mPolicyAccess = new PolicyAccess();
@@ -283,11 +282,6 @@ public class NotificationManagerService extends SystemService {
private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
private static final String ATTR_VERSION = "version";
// Obsolete: converted if present, but not resaved to disk.
private static final String TAG_BLOCKED_PKGS = "blocked-packages";
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
private RankingHelper mRankingHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
@@ -1259,10 +1253,13 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
// Don't allow client applications to cancel foreground service notis or autobundled
// summaries.
cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId,
(Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE)
| (Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_AUTOGROUP_SUMMARY), false, userId,
REASON_APP_CANCEL, null);
}
@@ -1404,7 +1401,9 @@ public class NotificationManagerService extends SystemService {
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) {
if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId
&& (sbn.getNotification().flags
& Notification.FLAG_AUTOGROUP_SUMMARY) != 0) {
// We could pass back a cloneLight() but clients might get confused and
// try to send this thing back to notify() again, which would not work
// very well.
@@ -1519,7 +1518,8 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystemOrSameApp(component.getPackageName());
long identity = Binder.clearCallingIdentity();
try {
ManagedServices manager = mRankerServices.isComponentEnabledForCurrentProfiles(component)
ManagedServices manager =
mRankerServices.isComponentEnabledForCurrentProfiles(component)
? mRankerServices
: mListeners;
manager.setComponentState(component, true);
@@ -2035,25 +2035,123 @@ public class NotificationManagerService extends SystemService {
}
@Override
public void setImportanceFromRankerService(INotificationListener token, String key,
int importance, CharSequence explanation) throws RemoteException {
if (importance == IMPORTANCE_NONE) {
throw new IllegalArgumentException("blocking not allowed: key=" + key);
}
public void applyAdjustmentFromRankerService(INotificationListener token,
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
mRankerServices.checkServiceTokenLocked(token);
NotificationRecord n = mNotificationsByKey.get(key);
n.setImportance(importance, explanation);
mRankingHandler.requestSort();
applyAdjustmentLocked(adjustment);
}
maybeAddAutobundleSummary(adjustment);
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void applyAdjustmentsFromRankerService(INotificationListener token,
List<Adjustment> adjustments) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
mRankerServices.checkServiceTokenLocked(token);
for (Adjustment adjustment : adjustments) {
applyAdjustmentLocked(adjustment);
}
}
for (Adjustment adjustment : adjustments) {
maybeAddAutobundleSummary(adjustment);
}
mRankingHandler.requestSort();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
};
private void applyAdjustmentLocked(Adjustment adjustment) {
maybeClearAutobundleSummaryLocked(adjustment);
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
if (n == null) {
return;
}
if (adjustment.getImportance() != IMPORTANCE_NONE) {
n.setImportance(adjustment.getImportance(), adjustment.getExplanation());
}
if (adjustment.getSignals() != null) {
Bundle.setDefusable(adjustment.getSignals(), true);
n.sbn.setOverrideGroupKey(adjustment.getSignals().getString(
Adjustment.GROUP_KEY_OVERRIDE_KEY, null));
}
}
// Clears the 'fake' auto-bunding summary.
private void maybeClearAutobundleSummaryLocked(Adjustment adjustment) {
if (adjustment.getSignals() != null
&& adjustment.getSignals().containsKey(Adjustment.NEEDS_AUTOGROUPING_KEY)
&& !adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
if (mAutobundledSummaries.containsKey(adjustment.getPackage())) {
// Clear summary.
final NotificationRecord removed = mNotificationsByKey.get(
mAutobundledSummaries.remove(adjustment.getPackage()));
if (removed != null) {
mNotificationList.remove(removed);
cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
}
}
}
}
// Posts a 'fake' summary for a package that has exceeded the solo-notification limit.
private void maybeAddAutobundleSummary(Adjustment adjustment) {
if (adjustment.getSignals() != null
&& adjustment.getSignals().getBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false)) {
final String newAutoBundleKey =
adjustment.getSignals().getString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
int userId = -1;
NotificationRecord summaryRecord = null;
synchronized (mNotificationList) {
if (!mAutobundledSummaries.containsKey(adjustment.getPackage())
&& newAutoBundleKey != null) {
// Add summary
final StatusBarNotification adjustedSbn
= mNotificationsByKey.get(adjustment.getKey()).sbn;
final ApplicationInfo appInfo =
adjustedSbn.getNotification().extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO);
final Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final Notification summaryNotification =
new Notification.Builder(getContext()).setSmallIcon(
adjustedSbn.getNotification().getSmallIcon())
.setGroupSummary(true)
.setGroup(newAutoBundleKey)
.setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
.setFlag(Notification.FLAG_GROUP_SUMMARY, true)
.build();
summaryNotification.extras.putAll(extras);
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
adjustedSbn.getOpPkg(),
Integer.MAX_VALUE, Adjustment.GROUP_KEY_OVERRIDE_KEY,
adjustedSbn.getUid(), adjustedSbn.getInitialPid(),
summaryNotification, adjustedSbn.getUser(), newAutoBundleKey,
System.currentTimeMillis());
summaryRecord = new NotificationRecord(getContext(), summarySbn);
mAutobundledSummaries.put(adjustment.getPackage(), summarySbn.getKey());
userId = adjustedSbn.getUser().getIdentifier();
}
}
if (summaryRecord != null) {
mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
}
}
}
private String disableNotificationEffects(NotificationRecord record) {
if (mDisableNotificationEffects) {
return "booleanState";
@@ -2253,6 +2351,17 @@ public class NotificationManagerService extends SystemService {
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = new UserHandle(userId);
// Fix the notification as best we can.
try {
Notification.addFieldsFromContext(getContext().createApplicationContext(
getContext().getPackageManager().getApplicationInfoAsUser(
pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId),
Context.CONTEXT_RESTRICTED), notification);
} catch (NameNotFoundException e) {
Slog.e(TAG, "Cannot create a context for sending app", e);
return;
}
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
@@ -2492,7 +2601,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotification sbn = r.sbn;
String group = sbn.getGroupKey();
boolean isSummary = sbn.getNotification().isGroupSummary();
boolean isChild = sbn.getNotification().isGroupChild();
boolean isChild = !isSummary && sbn.isGroup();
NotificationRecord summary = mSummaryByGroupKey.get(group);
if (isChild && summary != null) {
@@ -2857,11 +2966,13 @@ public class NotificationManagerService extends SystemService {
synchronized (mNotificationList) {
final int N = mNotificationList.size();
ArrayList<String> orderBefore = new ArrayList<String>(N);
ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
int[] visibilities = new int[N];
int [] importances = new int[N];
int[] importances = new int[N];
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
groupOverrideBefore.add(r.sbn.getGroupKey());
visibilities[i] = r.getPackageVisibilityOverride();
importances[i] = r.getImportance();
mRankingHelper.extractSignals(r);
@@ -2871,7 +2982,8 @@ public class NotificationManagerService extends SystemService {
final NotificationRecord r = mNotificationList.get(i);
if (!orderBefore.get(i).equals(r.getKey())
|| visibilities[i] != r.getPackageVisibilityOverride()
|| importances[i] != r.getImportance()) {
|| importances[i] != r.getImportance()
|| !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
scheduleSendRankingUpdate();
return;
}
@@ -3070,6 +3182,7 @@ public class NotificationManagerService extends SystemService {
mLights.remove(canceledKey);
// Record usage stats
// TODO: add unbundling stats?
switch (reason) {
case REASON_DELEGATE_CANCEL:
case REASON_DELEGATE_CANCEL_ALL:
@@ -3089,6 +3202,9 @@ public class NotificationManagerService extends SystemService {
if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
mSummaryByGroupKey.remove(groupKey);
}
if (r.sbn.getKey().equals(mAutobundledSummaries.get(r.sbn.getPackageName()))) {
mAutobundledSummaries.remove(r.sbn.getPackageName());
}
// Save it for users of getHistoricalNotifications()
mArchive.record(r.sbn);
@@ -3287,7 +3403,7 @@ public class NotificationManagerService extends SystemService {
for (int i = N - 1; i >= 0; i--) {
NotificationRecord childR = mNotificationList.get(i);
StatusBarNotification childSbn = childR.sbn;
if (childR.getNotification().isGroupChild() &&
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
childR.getGroupKey().equals(r.getGroupKey())) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
@@ -3438,6 +3554,7 @@ public class NotificationManagerService extends SystemService {
ArrayList<String> keys = new ArrayList<String>(N);
ArrayList<String> interceptedKeys = new ArrayList<String>(N);
ArrayList<Integer> importance = new ArrayList<>(N);
Bundle overrideGroupKeys = new Bundle();
Bundle visibilityOverrides = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
@@ -3461,6 +3578,7 @@ public class NotificationManagerService extends SystemService {
!= NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
}
overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -3470,7 +3588,7 @@ public class NotificationManagerService extends SystemService {
importanceAr[i] = importance.get(i);
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation);
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {

View File

@@ -24,18 +24,15 @@ import static android.service.notification.NotificationListenerService.Ranking.I
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
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.Build;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;

View File

@@ -86,6 +86,155 @@ public class NotificationTestList extends TestActivity
}
private Test[] mTests = new Test[] {
new Test("Post a group") {
public void run()
{
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("Min priority group 1")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.setGroup("group1")
.build();
mNM.notify(6000, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("low priority group 1")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_LOW)
.setGroup("group1")
.build();
mNM.notify(6001, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("default priority group 1")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_DEFAULT)
.setGroup("group1")
.build();
mNM.notify(6002, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("summary group 1")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.setGroup("group1")
.setGroupSummary(true)
.build();
mNM.notify(6003, n);
}
},
new Test("Post a group (2) w/o summary") {
public void run()
{
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("Min priority group 2")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.setGroup("group2")
.build();
mNM.notify(6100, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("low priority group 2")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_LOW)
.setGroup("group2")
.build();
mNM.notify(6101, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("default priority group 2")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_DEFAULT)
.setGroup("group2")
.build();
mNM.notify(6102, n);
}
},
new Test("Summary for group 2") {
public void run()
{
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("summary group 2")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.setGroup("group2")
.setGroupSummary(true)
.build();
mNM.notify(6103, n);
}
},
new Test("Group up public-secret") {
public void run()
{
Notification n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("public notification")
.setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setGroup("public-secret")
.build();
mNM.notify("public", 7009, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("private only notification")
.setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setGroup("public-secret")
.build();
mNM.notify("no public", 7010, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("private version of notification")
.setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setGroup("public-secret")
.setPublicVersion(new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("public notification of private notification")
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.build())
.build();
mNM.notify("priv with pub", 7011, n);
n = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("secret notification")
.setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_SECRET)
.setGroup("public-secret")
.build();
mNM.notify("secret", 7012, n);
Notification s = new Notification.Builder(NotificationTestList.this)
.setSmallIcon(R.drawable.icon2)
.setContentTitle("summary group public-secret")
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.setGroup("public-secret")
.setGroupSummary(true)
.build();
mNM.notify(7113, s);
}
},
new Test("Cancel priority autogroup") {
public void run()
{
try {
mNM.cancel(Integer.MAX_VALUE);
} catch (Exception e) {
Toast.makeText(NotificationTestList.this, "cancel failed (yay)",
Toast.LENGTH_LONG).show();
}
}
},
new Test("Min priority") {
public void run()
{
@@ -95,7 +244,7 @@ public class NotificationTestList extends TestActivity
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_MIN)
.build();
mNM.notify(7000, n);
mNM.notify("min", 7000, n);
}
},
new Test("Min priority, high pri flag") {
@@ -123,7 +272,7 @@ public class NotificationTestList extends TestActivity
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_LOW)
.build();
mNM.notify(7002, n);
mNM.notify("low", 7002, n);
}
},
new Test("Default priority") {
@@ -135,7 +284,7 @@ public class NotificationTestList extends TestActivity
.setLights(0xff0000ff, 1, 0)
.setPriority(Notification.PRIORITY_DEFAULT)
.build();
mNM.notify(7004, n);
mNM.notify("default", 7004, n);
}
},
new Test("High priority") {
@@ -150,7 +299,7 @@ public class NotificationTestList extends TestActivity
getPackageName() + "/raw/ringer"))
.setPriority(Notification.PRIORITY_HIGH)
.build();
mNM.notify(7006, n);
mNM.notify("high", 7006, n);
}
},
new Test("Max priority") {
@@ -166,7 +315,7 @@ public class NotificationTestList extends TestActivity
.setPriority(Notification.PRIORITY_MAX)
.setFullScreenIntent(makeIntent2(), false)
.build();
mNM.notify(7007, n);
mNM.notify("max", 7007, n);
}
},
new Test("Max priority with delay") {
@@ -199,7 +348,7 @@ public class NotificationTestList extends TestActivity
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.build();
mNM.notify(7009, n);
mNM.notify("public", 7009, n);
}
},
new Test("private notification, no public") {
@@ -212,7 +361,7 @@ public class NotificationTestList extends TestActivity
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.build();
mNM.notify(7010, n);
mNM.notify("no public", 7010, n);
}
},
new Test("private notification, has public") {
@@ -231,7 +380,7 @@ public class NotificationTestList extends TestActivity
.setVisibility(Notification.VISIBILITY_PUBLIC)
.build())
.build();
mNM.notify(7011, n);
mNM.notify("priv with pub", 7011, n);
}
},
new Test("secret notification") {
@@ -244,7 +393,7 @@ public class NotificationTestList extends TestActivity
.setPriority(Notification.PRIORITY_DEFAULT)
.setVisibility(Notification.VISIBILITY_SECRET)
.build();
mNM.notify(7012, n);
mNM.notify("secret", 7012, n);
}
},
new Test("Off") {