From baff400fa5a93d157934818982fcf534327f9830 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 15 Dec 2016 11:34:26 -0500 Subject: [PATCH] Add badging support for channels. 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 --- api/current.txt | 4 + api/system-current.txt | 7 ++ api/test-current.txt | 4 + core/java/android/app/INotificationManager.aidl | 7 -- core/java/android/app/NotificationChannel.java | 109 +++++++++++++++++-- core/java/android/app/NotificationManager.java | 2 +- .../systemui/statusbar/NotificationGuts.java | 9 -- .../server/notification/ImportanceExtractor.java | 14 +-- .../notification/NotificationManagerService.java | 97 ++++++----------- .../server/notification/PriorityExtractor.java | 7 +- .../android/server/notification/RankingConfig.java | 12 +-- .../android/server/notification/RankingHelper.java | 59 +++++------ .../server/notification/VisibilityExtractor.java | 7 +- .../notification/ImportanceExtractorTest.java | 58 +--------- .../NotificationManagerServiceTest.java | 76 ++++++++++++- .../server/notification/RankingHelperTest.java | 118 ++++++++++++++++++--- 16 files changed, 363 insertions(+), 227 deletions(-) diff --git a/api/current.txt b/api/current.txt index 395d7aac70c2..bf0c9f62711f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5381,6 +5381,7 @@ package android.app { 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(); @@ -5389,10 +5390,13 @@ package android.app { 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(); diff --git a/api/system-current.txt b/api/system-current.txt index f38b02e7a838..6ecbb021c6e5 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5538,6 +5538,7 @@ package android.app { 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(); @@ -5547,12 +5548,15 @@ package android.app { 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(); @@ -5562,9 +5566,12 @@ package android.app { method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException; field public static final android.os.Parcelable.Creator 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 diff --git a/api/test-current.txt b/api/test-current.txt index e43d1b5426c3..9eca670028ea 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5391,6 +5391,7 @@ package android.app { 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(); @@ -5399,10 +5400,13 @@ package android.app { 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(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 2c4e7a0871b9..211aa07e4351 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -50,13 +50,6 @@ interface INotificationManager 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, diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 79a01c29cb4a..52d9b67d5328 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -53,7 +53,9 @@ public final class NotificationChannel implements Parcelable { 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 = ","; /** @@ -87,10 +89,39 @@ public final class NotificationChannel implements Parcelable { @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; @@ -102,6 +133,8 @@ public final class NotificationChannel implements Parcelable { private long[] mVibration; private int mUserLockedFields; private boolean mVibrationEnabled; + private boolean mShowBadge; + private boolean mAllowed = DEFAULT_ALLOWED; /** * Creates a notification channel. @@ -137,6 +170,8 @@ public final class NotificationChannel implements Parcelable { mVibration = in.createLongArray(); mUserLockedFields = in.readInt(); mVibrationEnabled = in.readByte() != 0; + mShowBadge = in.readByte() != 0; + mAllowed = in.readByte() != 0; } @Override @@ -161,6 +196,8 @@ public final class NotificationChannel implements Parcelable { 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); } /** @@ -174,30 +211,30 @@ public final class NotificationChannel implements Parcelable { // 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}. @@ -206,6 +243,30 @@ public final class NotificationChannel implements Parcelable { 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. /** @@ -311,6 +372,21 @@ public final class NotificationChannel implements Parcelable { } /** + * 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 @@ -331,6 +407,8 @@ public final class NotificationChannel implements Parcelable { 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)); } @@ -369,6 +447,12 @@ public final class NotificationChannel implements Parcelable { 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); } @@ -398,6 +482,8 @@ public final class NotificationChannel implements Parcelable { 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; } @@ -481,6 +567,8 @@ public final class NotificationChannel implements Parcelable { 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; @@ -500,10 +588,11 @@ public final class NotificationChannel implements Parcelable { 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{" + @@ -517,6 +606,8 @@ public final class NotificationChannel implements Parcelable { ", mVibration=" + Arrays.toString(mVibration) + ", mUserLockedFields=" + mUserLockedFields + ", mVibrationEnabled=" + mVibrationEnabled + + ", mShowBadge=" + mShowBadge + + ", mAllowed=" + mAllowed + '}'; } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 7693d7666e19..a9ff482ffdba 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -202,7 +202,7 @@ public class NotificationManager 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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index ed1179aa0f63..58b828456b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -182,10 +182,6 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab 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 { @@ -220,11 +216,6 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab 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() { diff --git a/services/core/java/com/android/server/notification/ImportanceExtractor.java b/services/core/java/com/android/server/notification/ImportanceExtractor.java index 3bdc22c17383..46ec92b1bc9d 100644 --- a/services/core/java/com/android/server/notification/ImportanceExtractor.java +++ b/services/core/java/com/android/server/notification/ImportanceExtractor.java @@ -15,7 +15,6 @@ */ package com.android.server.notification; -import android.app.NotificationManager; import android.content.Context; import android.util.Slog; @@ -42,17 +41,8 @@ public class ImportanceExtractor implements NotificationSignalExtractor { 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; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a6fb4589f677..a3181e4146bb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1504,56 +1504,17 @@ public class NotificationManagerService extends SystemService { } @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); } @@ -1587,12 +1548,13 @@ public class NotificationManagerService extends SystemService { 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(); } @@ -2412,7 +2374,7 @@ public class NotificationManagerService extends SystemService { 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(); } @@ -2435,13 +2397,14 @@ public class NotificationManagerService extends SystemService { 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(); } @@ -2876,7 +2839,7 @@ public class NotificationManagerService extends SystemService { idOut[0] = id; } - private class EnqueueNotificationRunnable implements Runnable { + protected class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId; @@ -2935,23 +2898,9 @@ public class NotificationManagerService extends SystemService { 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 @@ -3018,6 +2967,28 @@ public class NotificationManagerService extends SystemService { 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; + } } /** diff --git a/services/core/java/com/android/server/notification/PriorityExtractor.java b/services/core/java/com/android/server/notification/PriorityExtractor.java index 666cf00dc2ce..5d5d39def13a 100644 --- a/services/core/java/com/android/server/notification/PriorityExtractor.java +++ b/services/core/java/com/android/server/notification/PriorityExtractor.java @@ -43,11 +43,8 @@ public class PriorityExtractor implements NotificationSignalExtractor { 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; } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 882e84c4a3b6..5c1e99c67710 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -20,19 +20,11 @@ import android.content.pm.ParceledListSlice; 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); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 98d4c6902f74..6474613f6d40 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -411,40 +411,6 @@ public class RankingHelper implements RankingConfig { } /** - * 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 @@ -453,7 +419,8 @@ public class RankingHelper implements RankingConfig { } @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()); @@ -473,6 +440,14 @@ public class RankingHelper implements RankingConfig { || 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); } @@ -480,6 +455,14 @@ public class RankingHelper implements RankingConfig { 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); @@ -534,6 +517,12 @@ public class RankingHelper implements RankingConfig { 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(); diff --git a/services/core/java/com/android/server/notification/VisibilityExtractor.java b/services/core/java/com/android/server/notification/VisibilityExtractor.java index 9d0e506fd68b..37dbe3a94995 100644 --- a/services/core/java/com/android/server/notification/VisibilityExtractor.java +++ b/services/core/java/com/android/server/notification/VisibilityExtractor.java @@ -43,12 +43,7 @@ public class VisibilityExtractor implements NotificationSignalExtractor { 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; } diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java index 6bc96754e200..eee9cf19bb7f 100644 --- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java +++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java @@ -97,11 +97,11 @@ public class ImportanceExtractorTest { 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); @@ -114,58 +114,6 @@ public class ImportanceExtractorTest { 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); } } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 80c4ccee078c..e1c0166ee39b 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,14 +16,21 @@ 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; @@ -31,6 +38,8 @@ import android.content.pm.ApplicationInfo; 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; @@ -42,8 +51,11 @@ import org.junit.runner.RunWith; @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 { @@ -51,12 +63,11 @@ public class NotificationManagerServiceTest { 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. @@ -92,4 +103,63 @@ public class NotificationManagerServiceTest { // 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); + } } diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index e6afe763bebf..5696a72ef8e2 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -246,8 +246,8 @@ public class RankingHelperTest { 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); @@ -273,7 +273,7 @@ public class RankingHelperTest { 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); @@ -296,7 +296,7 @@ public class RankingHelperTest { 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); @@ -355,7 +355,7 @@ public class RankingHelperTest { 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 @@ -369,7 +369,7 @@ public class RankingHelperTest { 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 = @@ -389,7 +389,7 @@ public class RankingHelperTest { 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 = @@ -410,7 +410,7 @@ public class RankingHelperTest { 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 = @@ -432,7 +432,7 @@ public class RankingHelperTest { 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 = @@ -453,7 +453,7 @@ public class RankingHelperTest { 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 = @@ -474,7 +474,7 @@ public class RankingHelperTest { 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 = @@ -488,6 +488,44 @@ public class RankingHelperTest { } @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 = @@ -497,7 +535,7 @@ public class RankingHelperTest { 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 = @@ -519,4 +557,60 @@ public class RankingHelperTest { 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()); + } } -- 2.11.0