[Notif] Allow locking importance on notification

Currently locking  only works on channel - this CL allows doing
so on the overarching notification too. Added locking field in
appropriate places in record and surfaced it to other bits via
the RankingHelper.

Test: Visually, reproduced organically (+ deleting /data/ files)
Fixes: 77775657
Change-Id: Ie46093921dd6c1ae3533ded7b87faaa475a631e4
This commit is contained in:
Rohan Shah
2018-04-10 23:48:47 -04:00
parent 9054656c20
commit 590e1b2aa5
8 changed files with 217 additions and 13 deletions

View File

@@ -54,6 +54,13 @@ interface INotificationManager
void setShowBadge(String pkg, int uid, boolean showBadge);
boolean canShowBadge(String pkg, int uid);
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
/**
* Updates the notification's enabled state. Additionally locks importance for all of the
* notifications belonging to the app, such that future notifications aren't reconsidered for
* blocking helper.
*/
void setNotificationsEnabledWithImportanceLockForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
boolean areNotificationsEnabled(String pkg);
int getPackageImportance(String pkg);

View File

@@ -523,7 +523,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
} else {
// For notifications with more than one channel, update notification enabled
// state. If the importance was lowered, we disable notifications.
mINotificationManager.setNotificationsEnabledForPackage(
mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
}
} catch (RemoteException e) {

View File

@@ -420,6 +420,66 @@ public class NotificationInfoTest extends SysuiTestCase {
assertEquals(IMPORTANCE_UNSPECIFIED, mNotificationChannel.getImportance());
}
@Test
public void testHandleCloseControls_setsNotificationsDisabledForMultipleChannelNotifications()
throws Exception {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
false /* isNonblockable */);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, times(1))
.setNotificationsEnabledWithImportanceLockForPackage(
anyString(), eq(TEST_UID), eq(false));
}
@Test
public void testHandleCloseControls_keepsNotificationsEnabledForMultipleChannelNotifications()
throws Exception {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
false /* isNonblockable */);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, times(1))
.setNotificationsEnabledWithImportanceLockForPackage(
anyString(), eq(TEST_UID), eq(false));
}
@Test
public void testCloseControls_blockingHelperSavesImportanceForMultipleChannelNotifications()
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
false /* isNonblockable */, true /* isForBlockingHelper */,
true /* isUserSentimentNegative */);
mNotificationInfo.findViewById(R.id.keep).performClick();
verify(mBlockingHelperManager).dismissCurrentBlockingHelper();
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, times(1))
.setNotificationsEnabledWithImportanceLockForPackage(
anyString(), eq(TEST_UID), eq(true));
}
@Test
public void testCloseControls_blockingHelperDismissedIfShown() throws Exception {
mNotificationInfo.bindNotification(

View File

@@ -2130,6 +2130,26 @@ public class NotificationManagerService extends SystemService {
savePolicyFile();
}
/**
* Updates the enabled state for notifications for the given package (and uid).
* Additionally, this method marks the app importance as locked by the user, which means
* that notifications from the app will <b>not</b> be considered for showing a
* blocking helper.
*
* @param pkg package that owns the notifications to update
* @param uid uid of the app providing notifications
* @param enabled whether notifications should be enabled for the app
*
* @see #setNotificationsEnabledForPackage(String, int, boolean)
*/
@Override
public void setNotificationsEnabledWithImportanceLockForPackage(
String pkg, int uid, boolean enabled) {
setNotificationsEnabledForPackage(pkg, uid, enabled);
mRankingHelper.setAppImportanceLocked(pkg, uid);
}
/**
* Use this when you just want to know if notifications are OK for this package.
*/
@@ -3616,6 +3636,8 @@ public class NotificationManagerService extends SystemService {
System.currentTimeMillis());
summaryRecord = new NotificationRecord(getContext(), summarySbn,
notificationRecord.getChannel());
summaryRecord.setIsAppImportanceLocked(
notificationRecord.getIsAppImportanceLocked());
summaries.put(pkg, summarySbn.getKey());
}
}
@@ -4012,6 +4034,7 @@ public class NotificationManagerService extends SystemService {
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
&& (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0

View File

@@ -151,11 +151,14 @@ public final class NotificationRecord {
private boolean mIsInterruptive;
private int mNumberOfSmartRepliesAdded;
private boolean mHasSeenSmartReplies;
/**
* Whether this notification (and its channels) should be considered user locked. Used in
* conjunction with user sentiment calculation.
*/
private boolean mIsAppImportanceLocked;
@VisibleForTesting
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel)
{
NotificationChannel channel) {
this.sbn = sbn;
mOriginalFlags = sbn.getNotification().flags;
mRankingTimeMs = calculateRankingTimeMs(0L);
@@ -503,6 +506,7 @@ public final class NotificationRecord {
pw.println(prefix + "mImportance="
+ NotificationListenerService.Ranking.importanceToString(mImportance));
pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
pw.println(prefix + "mIntercept=" + mIntercept);
pw.println(prefix + "mHidden==" + mHidden);
pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
@@ -564,11 +568,11 @@ public final class NotificationRecord {
public final String toString() {
return String.format(
"NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
": %s)",
"appImportanceLocked=%s: %s)",
System.identityHashCode(this),
this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
this.sbn.getNotification());
mIsAppImportanceLocked, this.sbn.getNotification());
}
public void addAdjustment(Adjustment adjustment) {
@@ -600,7 +604,8 @@ public final class NotificationRecord {
if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
// Only allow user sentiment update from assistant if user hasn't already
// expressed a preference for this channel
if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
if (!mIsAppImportanceLocked
&& (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
setUserSentiment(adjustment.getSignals().getInt(
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
}
@@ -609,6 +614,11 @@ public final class NotificationRecord {
}
}
public void setIsAppImportanceLocked(boolean isAppImportanceLocked) {
mIsAppImportanceLocked = isAppImportanceLocked;
calculateUserSentiment();
}
public void setContactAffinity(float contactAffinity) {
mContactAffinity = contactAffinity;
if (mImportance < IMPORTANCE_DEFAULT &&
@@ -870,6 +880,13 @@ public final class NotificationRecord {
return mChannel;
}
/**
* @see RankingHelper#getIsAppImportanceLocked(String, int)
*/
public boolean getIsAppImportanceLocked() {
return mIsAppImportanceLocked;
}
protected void updateNotificationChannel(NotificationChannel channel) {
if (channel != null) {
mChannel = channel;
@@ -927,7 +944,8 @@ public final class NotificationRecord {
}
private void calculateUserSentiment() {
if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0) {
if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0
|| mIsAppImportanceLocked) {
mUserSentiment = USER_SENTIMENT_POSITIVE;
}
}

View File

@@ -24,6 +24,7 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -36,7 +37,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.UserHandle;
@@ -89,11 +89,25 @@ public class RankingHelper implements RankingConfig {
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_SHOW_BADGE = true;
/**
* Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
* fields.
*/
private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
/**
* All user-lockable fields for a given application.
*/
@IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
public @interface LockableAppFields {
int USER_LOCKED_IMPORTANCE = 0x00000001;
}
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator;
@@ -218,6 +232,8 @@ public class RankingHelper implements RankingConfig {
parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
r.showBadge = XmlUtils.readBooleanAttribute(
parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
r.lockedAppFields = XmlUtils.readIntAttribute(parser,
ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
final int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -388,10 +404,14 @@ public class RankingHelper implements RankingConfig {
if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
continue;
}
final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
|| r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
|| r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0
|| r.groups.size() > 0;
final boolean hasNonDefaultSettings =
r.importance != DEFAULT_IMPORTANCE
|| r.priority != DEFAULT_PRIORITY
|| r.visibility != DEFAULT_VISIBILITY
|| r.showBadge != DEFAULT_SHOW_BADGE
|| r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
|| r.channels.size() > 0
|| r.groups.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -405,6 +425,8 @@ public class RankingHelper implements RankingConfig {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
Integer.toString(r.lockedAppFields));
if (!forBackup) {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -511,6 +533,17 @@ public class RankingHelper implements RankingConfig {
return getOrCreateRecord(packageName, uid).importance;
}
/**
* Returns whether the importance of the corresponding notification is user-locked and shouldn't
* be adjusted by an assistant (via means of a blocking helper, for example). For the channel
* locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
*/
public boolean getIsAppImportanceLocked(String packageName, int uid) {
int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
}
@Override
public boolean canShowBadge(String packageName, int uid) {
return getOrCreateRecord(packageName, uid).showBadge;
@@ -996,6 +1029,21 @@ public class RankingHelper implements RankingConfig {
enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
}
/**
* Sets whether any notifications from the app, represented by the given {@code pkgName} and
* {@code uid}, have their importance locked by the user. Locked notifications don't get
* considered for sentiment adjustments (and thus never show a blocking helper).
*/
public void setAppImportanceLocked(String packageName, int uid) {
Record record = getOrCreateRecord(packageName, uid);
if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
return;
}
record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
updateConfig();
}
@VisibleForTesting
void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
if (original.canBypassDnd() != update.canBypassDnd()) {
@@ -1413,6 +1461,7 @@ public class RankingHelper implements RankingConfig {
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
boolean showBadge = DEFAULT_SHOW_BADGE;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();

View File

@@ -553,6 +553,34 @@ public class NotificationRecordTest extends UiServiceTestCase {
assertEquals(USER_SENTIMENT_NEGATIVE, record.getUserSentiment());
}
@Test
public void testUserSentiment_appImportanceUpdatesSentiment() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(USER_SENTIMENT_NEUTRAL, record.getUserSentiment());
record.setIsAppImportanceLocked(true);
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
}
@Test
public void testUserSentiment_appImportanceBlocksNegativeSentimentUpdate() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
record.setIsAppImportanceLocked(true);
Bundle signals = new Bundle();
signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId()));
record.applyAdjustments();
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
}
@Test
public void testUserSentiment_userLocked() throws Exception {
channel.lockFields(USER_LOCKED_IMPORTANCE);
@@ -571,4 +599,18 @@ public class NotificationRecordTest extends UiServiceTestCase {
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
}
@Test
public void testAppImportance_returnsCorrectly() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
record.setIsAppImportanceLocked(true);
assertEquals(true, record.getIsAppImportanceLocked());
record.setIsAppImportanceLocked(false);
assertEquals(false, record.getIsAppImportanceLocked());
}
}

View File

@@ -371,6 +371,7 @@ public class RankingHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
mHelper.setShowBadge(PKG, UID, true);
mHelper.setAppImportanceLocked(PKG, UID);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
@@ -379,6 +380,7 @@ public class RankingHelperTest extends UiServiceTestCase {
loadStreamXml(baos, false);
assertTrue(mHelper.canShowBadge(PKG, UID));
assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
compareChannels(channel2,
mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
@@ -805,6 +807,7 @@ public class RankingHelperTest extends UiServiceTestCase {
assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
mHelper.getPackageVisibility(PKG, UID));
assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
NotificationChannel defaultChannel = mHelper.getNotificationChannel(
PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
@@ -814,6 +817,7 @@ public class RankingHelperTest extends UiServiceTestCase {
defaultChannel.setBypassDnd(true);
defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
mHelper.setAppImportanceLocked(PKG, UID);
mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
// ensure app level fields are changed
@@ -821,6 +825,7 @@ public class RankingHelperTest extends UiServiceTestCase {
assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
}
@Test