In this iteration badges are a user opt in feature.
Known issue: all listeners will receive 'badge only' notifications.
Test: runtest systemui-notification
Change-Id: Ic7450bf4de5351cfdc72bd96ec946fe6e035035c
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
+ method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getId();
method public java.lang.CharSequence getName();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
+ method public boolean isAllowed();
+ method public void setAllowed(boolean);
method public void setBypassDnd(boolean);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
+ method public void setShowBadge(boolean);
method public void setSound(android.net.Uri);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
+ method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getId();
method public android.net.Uri getSound();
method public int getUserLockedFields();
method public long[] getVibrationPattern();
+ method public boolean isAllowed();
method public void lockFields(int);
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
+ method public void setAllowed(boolean);
method public void setBypassDnd(boolean);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
+ method public void setShowBadge(boolean);
method public void setSound(android.net.Uri);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
+ field public static final int[] LOCKABLE_FIELDS;
+ field public static final int USER_LOCKED_ALLOWED = 64; // 0x40
field public static final int USER_LOCKED_IMPORTANCE = 4; // 0x4
field public static final int USER_LOCKED_LIGHTS = 8; // 0x8
field public static final int USER_LOCKED_PRIORITY = 1; // 0x1
+ field public static final int USER_LOCKED_SHOW_BADGE = 128; // 0x80
field public static final int USER_LOCKED_SOUND = 32; // 0x20
field public static final int USER_LOCKED_VIBRATION = 16; // 0x10
field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int);
ctor protected NotificationChannel(android.os.Parcel);
method public boolean canBypassDnd();
+ method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
method public java.lang.String getId();
method public java.lang.CharSequence getName();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
+ method public boolean isAllowed();
+ method public void setAllowed(boolean);
method public void setBypassDnd(boolean);
method public void setImportance(int);
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
+ method public void setShowBadge(boolean);
method public void setSound(android.net.Uri);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
boolean areNotificationsEnabled(String pkg);
-
- void setVisibilityOverride(String pkg, int uid, int visibility);
- int getVisibilityOverride(String pkg, int uid);
- void setPriority(String pkg, int uid, int priority);
- int getPriority(String pkg, int uid);
- void setImportance(String pkg, int uid, int importance);
- int getImportance(String pkg, int uid);
int getPackageImportance(String pkg);
void createNotificationChannel(String pkg, in NotificationChannel channel,
private static final String ATT_SOUND = "sound";
//TODO: add audio attributes support
private static final String ATT_AUDIO_ATTRIBUTES = "audio_attributes";
+ private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_USER_LOCKED = "locked";
+ private static final String ATT_ALLOWED = "allowed";
private static final String DELIMITER = ",";
/**
@SystemApi
public static final int USER_LOCKED_SOUND = 0x00000020;
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_LOCKED_ALLOWED = 0x00000040;
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int[] LOCKABLE_FIELDS = new int[] {
+ USER_LOCKED_PRIORITY,
+ USER_LOCKED_VISIBILITY,
+ USER_LOCKED_IMPORTANCE,
+ USER_LOCKED_LIGHTS,
+ USER_LOCKED_VIBRATION,
+ USER_LOCKED_SOUND,
+ USER_LOCKED_ALLOWED,
+ USER_LOCKED_SHOW_BADGE
+ };
+
+
private static final int DEFAULT_VISIBILITY =
NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE =
NotificationManager.IMPORTANCE_UNSPECIFIED;
+ private static final boolean DEFAULT_ALLOWED = true;
private final String mId;
private CharSequence mName;
private long[] mVibration;
private int mUserLockedFields;
private boolean mVibrationEnabled;
+ private boolean mShowBadge;
+ private boolean mAllowed = DEFAULT_ALLOWED;
/**
* Creates a notification channel.
mVibration = in.createLongArray();
mUserLockedFields = in.readInt();
mVibrationEnabled = in.readByte() != 0;
+ mShowBadge = in.readByte() != 0;
+ mAllowed = in.readByte() != 0;
}
@Override
dest.writeLongArray(mVibration);
dest.writeInt(mUserLockedFields);
dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
+ dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
+ dest.writeByte(mAllowed ? (byte) 1 : (byte) 0);
}
/**
// Modifiable by a notification ranker.
/**
- * Only modifiable by the system and notification ranker.
- *
- * Sets whether or not this notification can interrupt the user in
+ * Sets whether or not notifications posted to this channel can interrupt the user in
* {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+ *
+ * Only modifiable by the system and notification ranker.
*/
public void setBypassDnd(boolean bypassDnd) {
this.mBypassDnd = bypassDnd;
}
/**
- * Only modifiable by the system and notification ranker.
+ * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
+ * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
*
- * Sets whether this notification appears on the lockscreen or not, and if so, whether it
- * appears in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+ * Only modifiable by the system and notification ranker.
*/
public void setLockscreenVisibility(int lockscreenVisibility) {
this.mLockscreenVisibility = lockscreenVisibility;
}
/**
- * Only modifiable by the system and notification ranker.
- *
* Sets the level of interruption of this notification channel.
*
+ * Only modifiable by the system and notification ranker.
+ *
* @param importance the amount the user should be interrupted by notifications from this
* channel. See e.g.
* {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
this.mImportance = importance;
}
+ /**
+ * Sets whether notifications posted to this channel can appear as application icon badges
+ * in a Launcher.
+ *
+ * Only modifiable by the system and notification ranker.
+ *
+ * @param showBadge true if badges should be allowed to be shown.
+ */
+ public void setShowBadge(boolean showBadge) {
+ this.mShowBadge = showBadge;
+ }
+
+ /**
+ * Sets whether notifications are allowed to be posted to this channel.
+ *
+ * Only modifiable by the system and notification ranker.
+ *
+ * @param allowed true if notifications are not allowed from this channel.
+ */
+ public void setAllowed(boolean allowed) {
+ this.mAllowed = allowed;
+ }
+
+
// Modifiable by apps on channel creation.
/**
}
/**
+ * Returns whether notifications posted to this channel can appear as badges in a Launcher
+ * application.
+ */
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
+ /**
+ * Returns whether notifications are allowed to post to this channel.
+ */
+ public boolean isAllowed() {
+ return mAllowed;
+ }
+
+ /**
* @hide
*/
@SystemApi
setLights(safeBool(parser, ATT_LIGHTS, false));
enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
+ setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
+ setAllowed(safeBool(parser, ATT_ALLOWED, true));
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
}
if (getUserLockedFields() != 0) {
out.attribute(null, ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
}
+ if (canShowBadge()) {
+ out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
+ }
+ if (!isAllowed()) {
+ out.attribute(null, ATT_ALLOWED, Boolean.toString(isAllowed()));
+ }
out.endTag(null, TAG_CHANNEL);
}
record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
+ record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
+ record.put(ATT_ALLOWED, Boolean.toString(isAllowed()));
return record;
}
if (mLights != that.mLights) return false;
if (mUserLockedFields != that.mUserLockedFields) return false;
if (mVibrationEnabled != that.mVibrationEnabled) return false;
+ if (mShowBadge != that.mShowBadge) return false;
+ if (mAllowed != that.mAllowed) return false;
if (mId != null ? !mId.equals(that.mId) : that.mId != null) return false;
if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
if (mSound != null ? !mSound.equals(that.mSound) : that.mSound != null) return false;
result = 31 * result + Arrays.hashCode(mVibration);
result = 31 * result + mUserLockedFields;
result = 31 * result + (mVibrationEnabled ? 1 : 0);
+ result = 31 * result + (mShowBadge ? 1 : 0);
+ result = 31 * result + (mAllowed ? 1 : 0);
return result;
}
-
@Override
public String toString() {
return "NotificationChannel{" +
", mVibration=" + Arrays.toString(mVibration) +
", mUserLockedFields=" + mUserLockedFields +
", mVibrationEnabled=" + mVibrationEnabled +
+ ", mShowBadge=" + mShowBadge +
+ ", mAllowed=" + mAllowed +
'}';
}
}
public static final int IMPORTANCE_UNSPECIFIED = -1000;
/**
- * A notification with no importance: shows nowhere, is blocked.
+ * A notification with no importance: does not show in the shade.
*/
public static final int IMPORTANCE_NONE = 0;
mINotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mStartingUserImportance = NotificationManager.IMPORTANCE_UNSPECIFIED;
- try {
- mStartingUserImportance =
- mINotificationManager.getImportance(sbn.getPackageName(), sbn.getUid());
- } catch (RemoteException e) {}
mNotificationImportance = importance;
boolean nonBlockable = false;
try {
int progress = getSelectedImportance();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
progress - mStartingUserImportance);
- try {
- mINotificationManager.setImportance(sbn.getPackageName(), sbn.getUid(), progress);
- } catch (RemoteException e) {
- // :(
- }
}
private int getSelectedImportance() {
*/
package com.android.server.notification;
-import android.app.NotificationManager;
import android.content.Context;
import android.util.Slog;
if (DBG) Slog.d(TAG, "missing config");
return null;
}
- int importance = NotificationManager.IMPORTANCE_UNSPECIFIED;
- int appImportance = mConfig.getImportance(
- record.sbn.getPackageName(), record.sbn.getUid());
- int channelImportance = record.getChannel().getImportance();
- if (appImportance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
- record.setUserImportance(channelImportance);
- } else if (channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
- record.setUserImportance(appImportance);
- } else {
- record.setUserImportance(Math.min(appImportance, channelImportance));
- }
+ record.setUserImportance(record.getChannel().getImportance());
+
return null;
}
}
@Override
- public void setPriority(String pkg, int uid, int priority) {
- checkCallerIsSystem();
- mRankingHelper.setPriority(pkg, uid, priority);
- savePolicyFile();
- }
-
- @Override
- public int getPriority(String pkg, int uid) {
- checkCallerIsSystem();
- return mRankingHelper.getPriority(pkg, uid);
- }
-
- @Override
- public void setVisibilityOverride(String pkg, int uid, int visibility) {
- checkCallerIsSystem();
- mRankingHelper.setVisibilityOverride(pkg, uid, visibility);
- savePolicyFile();
- }
-
- @Override
- public int getVisibilityOverride(String pkg, int uid) {
- checkCallerIsSystem();
- return mRankingHelper.getVisibilityOverride(pkg, uid);
- }
-
- @Override
- public void setImportance(String pkg, int uid, int importance) {
- enforceSystemOrSystemUI("Caller not system or systemui");
- setNotificationsEnabledForPackageImpl(pkg, uid, importance != IMPORTANCE_NONE);
- mRankingHelper.setImportance(pkg, uid, importance);
- savePolicyFile();
- }
-
- @Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
}
@Override
- public int getImportance(String pkg, int uid) {
- enforceSystemOrSystemUI("Caller not system or systemui");
- return mRankingHelper.getImportance(pkg, uid);
- }
-
- @Override
public void createNotificationChannel(String pkg, NotificationChannel channel,
IOnNotificationChannelCreatedListener listener) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
- mRankingHelper.createNotificationChannel(pkg, Binder.getCallingUid(), channel);
+ mRankingHelper.createNotificationChannel(pkg, Binder.getCallingUid(), channel,
+ true /* fromTargetApp */);
savePolicyFile();
listener.onNotificationChannelCreated(channel);
}
public void updateNotificationChannelForPackage(String pkg, int uid,
NotificationChannel channel) {
checkCallerIsSystem();
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
+ if (!channel.isAllowed()) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+ null);
}
mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+ mRankingHandler.requestSort(true);
savePolicyFile();
}
NotificationChannel channel) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- mRankingHelper.createNotificationChannel(pkg, uid, channel);
+ mRankingHelper.createNotificationChannel(pkg, uid, channel, false /* fromTargetApp */);
savePolicyFile();
}
public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
NotificationChannel channel) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ if (!channel.isAllowed()) {
// cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
info.userid, REASON_CHANNEL_BANNED, null);
}
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ mRankingHandler.requestSort(true);
savePolicyFile();
}
idOut[0] = id;
}
- private class EnqueueNotificationRunnable implements Runnable {
+ protected class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
mRankingHelper.extractSignals(r);
- final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
-
// blocked apps
- if (r.getImportance() == NotificationManager.IMPORTANCE_NONE
- || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE
- || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
- if (!isSystemNotification) {
- if (isPackageSuspended) {
- Slog.e(TAG, "Suppressing notification from package due to package "
- + "suspended by administrator.");
- mUsageStats.registerSuspendedByAdmin(r);
- } else {
- Slog.e(TAG, "Suppressing notification from package by user request.");
- mUsageStats.registerBlocked(r);
- }
- return;
- }
+ if (isBlocked(r, mUsageStats)) {
+ return;
}
// tell the assistant service about the notification
buzzBeepBlinkLocked(r);
}
}
+
+ protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
+ final String pkg = r.sbn.getPackageName();
+ final int callingUid = r.sbn.getUid();
+
+ final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
+ if (isPackageSuspended) {
+ Slog.e(TAG, "Suppressing notification from package due to package "
+ + "suspended by administrator.");
+ usageStats.registerSuspendedByAdmin(r);
+ return isPackageSuspended;
+ }
+
+ final boolean isBlocked = r.getImportance() == NotificationManager.IMPORTANCE_NONE
+ || !r.getChannel().isAllowed()
+ || !noteNotificationOp(pkg, callingUid);
+ if (isBlocked) {
+ Slog.e(TAG, "Suppressing notification from package by user request.");
+ usageStats.registerBlocked(r);
+ }
+ return isBlocked;
+ }
}
/**
return null;
}
- int priority = mConfig.getPriority(record.sbn.getPackageName(), record.sbn.getUid());
- if (priority == Notification.PRIORITY_DEFAULT && record.getChannel().canBypassDnd()){
- priority = Notification.PRIORITY_MAX;
- }
- record.setPackagePriority(priority);
+ record.setPackagePriority(record.getChannel().canBypassDnd()
+ ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT);
return null;
}
public interface RankingConfig {
- int getPriority(String packageName, int uid);
-
- void setPriority(String packageName, int uid, int priority);
-
- int getVisibilityOverride(String packageName, int uid);
-
- void setVisibilityOverride(String packageName, int uid, int visibility);
-
void setImportance(String packageName, int uid, int importance);
-
int getImportance(String packageName, int uid);
- void createNotificationChannel(String pkg, int uid, NotificationChannel channel);
+ void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
}
/**
- * Gets priority.
- */
- @Override
- public int getPriority(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).priority;
- }
-
- /**
- * Sets priority.
- */
- @Override
- public void setPriority(String packageName, int uid, int priority) {
- getOrCreateRecord(packageName, uid).priority = priority;
- updateConfig();
- }
-
- /**
- * Gets visual override.
- */
- @Override
- public int getVisibilityOverride(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).visibility;
- }
-
- /**
- * Sets visibility override.
- */
- @Override
- public void setVisibilityOverride(String pkgName, int uid, int visibility) {
- getOrCreateRecord(pkgName, uid).visibility = visibility;
- updateConfig();
- }
-
- /**
* Gets importance.
*/
@Override
}
@Override
- public void createNotificationChannel(String pkg, int uid, NotificationChannel channel) {
+ public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromTargetApp) {
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(channel);
Preconditions.checkNotNull(channel.getId());
|| channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
throw new IllegalArgumentException("Invalid importance level");
}
+ // Reset fields that apps aren't allowed to set.
+ if (fromTargetApp) {
+ channel.setShowBadge(false);
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ channel.setLockscreenVisibility(r.visibility);
+ }
+ channel.setAllowed(true);
+ clearLockedFields(channel);
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
}
updateConfig();
}
+ private void clearLockedFields(NotificationChannel channel) {
+ int clearMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ clearMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(~clearMask);
+ }
+
@Override
public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
Preconditions.checkNotNull(updatedChannel);
channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility());
}
}
+ if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_ALLOWED) == 0) {
+ channel.setAllowed(updatedChannel.isAllowed());
+ }
+ if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) {
+ channel.setShowBadge(updatedChannel.canShowBadge());
+ }
r.channels.put(channel.getId(), channel);
updateConfig();
return null;
}
- int visibility =
- mConfig.getVisibilityOverride(record.sbn.getPackageName(), record.sbn.getUid());
- if (visibility == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
- visibility = record.getChannel().getLockscreenVisibility();
- }
- record.setPackageVisibilityOverride(visibility);
+ record.setPackageVisibilityOverride(record.getChannel().getLockscreenVisibility());
return null;
}
extractor.process(r);
- assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_UNSPECIFIED);
}
@Test
- public void testAppPreferenceChannelPermissive() throws Exception {
+ public void testAppPreferenceChannelPreference() throws Exception {
ImportanceExtractor extractor = new ImportanceExtractor();
extractor.setConfig(mConfig);
extractor.process(r);
- assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
- }
-
- @Test
- public void testAppPreferenceChannelStrict() throws Exception {
- ImportanceExtractor extractor = new ImportanceExtractor();
- extractor.setConfig(mConfig);
-
- when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
- NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel =
- new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_MIN);
-
- NotificationRecord r = getNotificationRecord(channel);
-
- extractor.process(r);
-
- assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
- }
-
- @Test
- public void testNoAppPreferenceChannelPreference() throws Exception {
- ImportanceExtractor extractor = new ImportanceExtractor();
- extractor.setConfig(mConfig);
-
- when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
- NotificationManager.IMPORTANCE_UNSPECIFIED);
- NotificationChannel channel =
- new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_MIN);
-
- NotificationRecord r = getNotificationRecord(channel);
-
- extractor.process(r);
-
- assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
- }
-
- @Test
- public void testNoPreferences() throws Exception {
- ImportanceExtractor extractor = new ImportanceExtractor();
- extractor.setConfig(mConfig);
-
- when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
- NotificationManager.IMPORTANCE_UNSPECIFIED);
- NotificationChannel channel =
- new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
-
- NotificationRecord r = getNotificationRecord(channel);
-
- extractor.process(r);
-
- assertEquals(r.getUserImportance(),
- NotificationManager.IMPORTANCE_UNSPECIFIED);
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_HIGH);
}
}
package com.android.server.notification;
+import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.app.IOnNotificationChannelCreatedListener;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.os.Binder;
import android.os.Handler;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NotificationManagerServiceTest {
+ private final String pkg = "com.android.server.notification";
+ private final int uid = 0;
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
+ private IPackageManager mPackageManager = mock(IPackageManager.class);
@Before
public void setUp() throws Exception {
mNotificationManagerService = new NotificationManagerService(context);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
- final IPackageManager mockPackageManager = mock(IPackageManager.class);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = Binder.getCallingUid();
- when(mockPackageManager.getApplicationInfo(any(), anyInt(), anyInt()))
+ when(mPackageManager.getApplicationInfo(any(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
- mNotificationManagerService.setPackageManager(mockPackageManager);
+ mNotificationManagerService.setPackageManager(mPackageManager);
mNotificationManagerService.setHandler(new Handler(context.getMainLooper()));
// Tests call directly into the Binder.
// pass
}
}
+
+ @Test
+ public void testBlockedNotifications_suspended() throws Exception {
+ NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
+
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ NotificationRecord r = generateNotificationRecord(channel);
+ NotificationManagerService.EnqueueNotificationRunnable enqueue =
+ mNotificationManagerService.new EnqueueNotificationRunnable(UserHandle.USER_SYSTEM,
+ r);
+ assertTrue(enqueue.isBlocked(r, usageStats));
+ verify(usageStats, times(1)).registerSuspendedByAdmin(eq(r));
+ }
+
+ @Test
+ public void testBlockedNotifications_blockedChannel() throws Exception {
+ NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ channel.setAllowed(false);
+ NotificationRecord r = generateNotificationRecord(channel);
+ NotificationManagerService.EnqueueNotificationRunnable enqueue =
+ mNotificationManagerService.new EnqueueNotificationRunnable(UserHandle.USER_SYSTEM,
+ r);
+ assertTrue(enqueue.isBlocked(r, usageStats));
+ verify(usageStats, times(1)).registerBlocked(eq(r));
+ }
+
+ @Test
+ public void testBlockedNotifications_blockedApp() throws Exception {
+ NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ NotificationRecord r = generateNotificationRecord(channel);
+ r.setUserImportance(NotificationManager.IMPORTANCE_NONE);
+ NotificationManagerService.EnqueueNotificationRunnable enqueue =
+ mNotificationManagerService.new EnqueueNotificationRunnable(UserHandle.USER_SYSTEM,
+ r);
+ assertTrue(enqueue.isBlocked(r, usageStats));
+ verify(usageStats, times(1)).registerBlocked(eq(r));
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ Notification n = new Notification.Builder(context)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, channel, 1, "tag", uid, uid,
+ n, UserHandle.SYSTEM, null, uid);
+ return new NotificationRecord(context, sbn);
+ }
}
channel2.enableVibration(true);
channel2.setVibrationPattern(new long[] {100, 67, 145, 156});
- mHelper.createNotificationChannel(pkg, uid, channel1);
- mHelper.createNotificationChannel(pkg, uid, channel2);
+ mHelper.createNotificationChannel(pkg, uid, channel1, false);
+ mHelper.createNotificationChannel(pkg, uid, channel2, false);
ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, channel1);
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
- mHelper.createNotificationChannel(pkg, uid, channel1);
+ mHelper.createNotificationChannel(pkg, uid, channel1, true);
final NotificationChannel defaultChannel =
mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
try {
mHelper.createNotificationChannel(pkg, uid,
- new NotificationChannel(pkg, "", NotificationManager.IMPORTANCE_LOW));
+ new NotificationChannel(pkg, "", NotificationManager.IMPORTANCE_LOW), true);
fail("Channel creation should fail");
} catch (IllegalArgumentException e) {
// pass
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update
final NotificationChannel channel2 =
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update
final NotificationChannel channel2 =
channel.setLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update
final NotificationChannel channel2 =
channel.setLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update
final NotificationChannel channel2 =
channel.setBypassDnd(true);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
channel.setSound(new Uri.Builder().scheme("test").build());
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
}
@Test
+ public void testUpdate_userLockedAllowed() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setAllowed(true);
+ channel.lockFields(NotificationChannel.USER_LOCKED_ALLOWED);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
+
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setAllowed(false);
+
+ mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedBadge() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setShowBadge(true);
+ channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
+
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setShowBadge(false);
+
+ mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
public void testUpdate() throws Exception {
// no fields locked by user
final NotificationChannel channel =
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.createNotificationChannel(pkg, uid, channel);
+ mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage");
assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
}
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ channel.setAllowed(false);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(pkg, uid, channel.getId());
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertFalse(savedChannel.canShowBadge());
+ }
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ channel.setAllowed(false);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(pkg, uid, channel, true);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(pkg, uid, channel.getId());
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertFalse(savedChannel.canShowBadge());
+ }
}