Make bubble settings a pref with an int rather than a bool am: a92268cd01

Change-Id: Icbbfcc38f9cf065c437a7de14ab3cf94d3c05d0d
This commit is contained in:
Mady Mellor
2020-04-14 11:27:42 +00:00
committed by Automerger Merge Worker
12 changed files with 655 additions and 625 deletions

View File

@@ -78,10 +78,9 @@ interface INotificationManager
boolean shouldHideSilentStatusIcons(String callingPkg);
void setHideSilentStatusIcons(boolean hide);
void setBubblesAllowed(String pkg, int uid, boolean allowed);
void setBubblesAllowed(String pkg, int uid, int bubblePreference);
boolean areBubblesAllowed(String pkg);
boolean areBubblesAllowedForPackage(String pkg, int uid);
boolean hasUserApprovedBubblesForPackage(String pkg, int uid);
int getBubblePreferenceForPackage(String pkg, int uid);
void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
void createNotificationChannels(String pkg, in ParceledListSlice channelsList);

View File

@@ -102,7 +102,7 @@ public final class NotificationChannel implements Parcelable {
private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
private static final String ATT_GROUP = "group";
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
private static final String ATT_ALLOW_BUBBLE = "can_bubble";
private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
private static final String ATT_ORIG_IMP = "orig_imp";
private static final String ATT_PARENT_CHANNEL = "parent";
private static final String ATT_CONVERSATION_ID = "conv_id";
@@ -168,7 +168,7 @@ public final class NotificationChannel implements Parcelable {
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
private static final boolean DEFAULT_ALLOW_BUBBLE = true;
private static final boolean DEFAULT_ALLOW_BUBBLE = false;
@UnsupportedAppUsage
private String mId;
@@ -545,15 +545,8 @@ public final class NotificationChannel implements Parcelable {
}
/**
* Sets whether notifications posted to this channel can appear outside of the notification
* shade, floating over other apps' content as a bubble.
*
* <p>This value will be ignored for channels that aren't allowed to pop on screen (that is,
* channels whose {@link #getImportance() importance} is <
* {@link NotificationManager#IMPORTANCE_HIGH}.</p>
*
* <p>Only modifiable before the channel is submitted to
* * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.</p>
* As of Android 11 this value is no longer respected.
* @see #canBubble()
* @see Notification#getBubbleMetadata()
*/
public void setAllowBubbles(boolean allowBubbles) {
@@ -702,8 +695,10 @@ public final class NotificationChannel implements Parcelable {
}
/**
* Returns whether notifications posted to this channel can display outside of the notification
* shade, in a floating window on top of other apps.
* Returns whether notifications posted to this channel are allowed to display outside of the
* notification shade, in a floating window on top of other apps.
*
* @see Notification#getBubbleMetadata()
*/
public boolean canBubble() {
return mAllowBubbles;

View File

@@ -452,6 +452,19 @@ public class NotificationManager {
*/
public static final int IMPORTANCE_MAX = 5;
/**
* @hide
*/
public static final int BUBBLE_PREFERENCE_NONE = 0;
/**
* @hide
*/
public static final int BUBBLE_PREFERENCE_ALL = 1;
/**
* @hide
*/
public static final int BUBBLE_PREFERENCE_SELECTED = 2;
@UnsupportedAppUsage
private static INotificationManager sService;
@@ -1213,7 +1226,7 @@ public class NotificationManager {
/**
* Sets whether notifications posted by this app can appear outside of the
* Gets whether all notifications posted by this app can appear outside of the
* notification shade, floating over other apps' content.
*
* <p>This value will be ignored for notifications that are posted to channels that do not

View File

@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row;
import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -598,6 +600,13 @@ public class NotificationConversationInfo extends LinearLayout implements
!mChannelToUpdate.isImportantConversation());
if (mChannelToUpdate.isImportantConversation()) {
mChannelToUpdate.setAllowBubbles(true);
int currentPref =
mINotificationManager.getBubblePreferenceForPackage(
mAppPkg, mAppUid);
if (currentPref == BUBBLE_PREFERENCE_NONE) {
mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
BUBBLE_PREFERENCE_SELECTED);
}
}
mChannelToUpdate.setImportance(Math.max(
mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));

View File

@@ -16,12 +16,17 @@
package com.android.server.notification;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -32,13 +37,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
/**
* Determines whether a bubble can be shown for this notification
* Determines whether a bubble can be shown for this notification.
*/
public class BubbleExtractor implements NotificationSignalExtractor {
private static final String TAG = "BubbleExtractor";
private static final boolean DBG = false;
private BubbleChecker mBubbleChecker;
private ShortcutHelper mShortcutHelper;
private RankingConfig mConfig;
private ActivityManager mActivityManager;
private Context mContext;
@@ -60,24 +65,34 @@ public class BubbleExtractor implements NotificationSignalExtractor {
return null;
}
if (mBubbleChecker == null) {
if (DBG) Slog.d(TAG, "missing bubble checker");
if (mShortcutHelper == null) {
if (DBG) Slog.d(TAG, "missing shortcut helper");
return null;
}
boolean appCanShowBubble =
mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid());
if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
int bubblePreference =
mConfig.getBubblePreference(
record.getSbn().getPackageName(), record.getSbn().getUid());
NotificationChannel recordChannel = record.getChannel();
if (!mConfig.bubblesEnabled() || bubblePreference == BUBBLE_PREFERENCE_NONE) {
record.setAllowBubble(false);
} else {
if (record.getChannel() != null) {
record.setAllowBubble(record.getChannel().canBubble() && appCanShowBubble);
} else {
record.setAllowBubble(appCanShowBubble);
}
} else if (recordChannel == null) {
// the app is allowed but there's no channel to check
record.setAllowBubble(true);
} else if (bubblePreference == BUBBLE_PREFERENCE_ALL) {
// by default the channel is not allowed, only don't bubble if the user specified
boolean userLockedNoBubbles = !recordChannel.canBubble()
&& (recordChannel.getUserLockedFields() & USER_LOCKED_ALLOW_BUBBLE) != 0;
record.setAllowBubble(!userLockedNoBubbles);
} else if (bubblePreference == BUBBLE_PREFERENCE_SELECTED) {
record.setAllowBubble(recordChannel.canBubble());
}
final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record)
&& !record.isFlagBubbleRemoved();
final boolean fulfillsPolicy = record.isConversation()
&& !mActivityManager.isLowRamDevice()
&& record.canBubble();
final boolean applyFlag = fulfillsPolicy && canPresentAsBubble(record);
if (applyFlag) {
record.getNotification().flags |= FLAG_BUBBLE;
} else {
@@ -95,165 +110,95 @@ public class BubbleExtractor implements NotificationSignalExtractor {
public void setZenHelper(ZenModeHelper helper) {
}
/**
* Expected to be called after {@link #setConfig(RankingConfig)} has occurred.
*/
void setShortcutHelper(ShortcutHelper helper) {
if (mConfig == null) {
if (DBG) Slog.d(TAG, "setting shortcut helper prior to setConfig");
return;
}
mBubbleChecker = new BubbleChecker(mContext, helper, mConfig, mActivityManager);
public void setShortcutHelper(ShortcutHelper helper) {
mShortcutHelper = helper;
}
@VisibleForTesting
void setBubbleChecker(BubbleChecker checker) {
mBubbleChecker = checker;
public void setActivityManager(ActivityManager manager) {
mActivityManager = manager;
}
/**
* Encapsulates special checks to see if a notification can be flagged as a bubble. This
* makes testing a bit easier.
* @return whether there is valid information for the notification to bubble.
*/
public static class BubbleChecker {
private ActivityManager mActivityManager;
private RankingConfig mRankingConfig;
private Context mContext;
private ShortcutHelper mShortcutHelper;
BubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
ActivityManager activityManager) {
mContext = context;
mActivityManager = activityManager;
mShortcutHelper = helper;
mRankingConfig = config;
@VisibleForTesting
boolean canPresentAsBubble(NotificationRecord r) {
Notification notification = r.getNotification();
Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
String pkg = r.getSbn().getPackageName();
if (metadata == null) {
return false;
}
/**
* @return whether the provided notification record is allowed to be represented as a
* bubble, accounting for user choice & policy.
*/
public boolean isNotificationAppropriateToBubble(NotificationRecord r) {
final String pkg = r.getSbn().getPackageName();
final int userId = r.getSbn().getUser().getIdentifier();
Notification notification = r.getNotification();
if (!canBubble(r, pkg, userId)) {
// no log: canBubble has its own
return false;
}
if (mActivityManager.isLowRamDevice()) {
logBubbleError(r.getKey(), "low ram device");
return false;
}
boolean isMessageStyle = Notification.MessagingStyle.class.equals(
notification.getNotificationStyle());
if (!isMessageStyle) {
logBubbleError(r.getKey(), "must be Notification.MessageStyle");
return false;
}
String shortcutId = metadata.getShortcutId();
String notificationShortcutId = r.getShortcutInfo() != null
? r.getShortcutInfo().getId()
: null;
boolean shortcutValid = false;
if (notificationShortcutId != null && shortcutId != null) {
// NoMan already checks validity of shortcut, just check if they match.
shortcutValid = shortcutId.equals(notificationShortcutId);
} else if (shortcutId != null) {
shortcutValid =
mShortcutHelper.getValidShortcutInfo(shortcutId, pkg, r.getUser()) != null;
}
if (metadata.getIntent() == null && !shortcutValid) {
// Should have a shortcut if intent is null
logBubbleError(r.getKey(),
"couldn't find valid shortcut for bubble with shortcutId: " + shortcutId);
return false;
}
if (shortcutValid) {
// TODO: check the shortcut intent / ensure it can show in activity view
return true;
}
return canLaunchInActivityView(mContext, metadata.getIntent(), pkg);
}
/**
* @return whether the user has enabled the provided notification to bubble, and if the
* developer has provided valid information for the notification to bubble.
*/
@VisibleForTesting
boolean canBubble(NotificationRecord r, String pkg, int userId) {
Notification notification = r.getNotification();
Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
if (metadata == null) {
// no log: no need to inform dev if they didn't attach bubble metadata
return false;
}
if (!mRankingConfig.bubblesEnabled()) {
logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
return false;
}
if (!mRankingConfig.areBubblesAllowed(pkg, userId)) {
logBubbleError(r.getKey(),
"bubbles for package: " + pkg + " disabled for user: " + userId);
return false;
}
if (!r.getChannel().canBubble()) {
logBubbleError(r.getKey(),
"bubbles for channel " + r.getChannel().getId() + " disabled");
return false;
}
String shortcutId = metadata.getShortcutId();
String notificationShortcutId = r.getShortcutInfo() != null
? r.getShortcutInfo().getId()
: null;
boolean shortcutValid = false;
if (notificationShortcutId != null && shortcutId != null) {
// NoMan already checks validity of shortcut, just check if they match.
shortcutValid = shortcutId.equals(notificationShortcutId);
} else if (shortcutId != null) {
shortcutValid =
mShortcutHelper.getValidShortcutInfo(shortcutId, pkg, r.getUser()) != null;
}
if (metadata.getIntent() == null && !shortcutValid) {
// Should have a shortcut if intent is null
logBubbleError(r.getKey(),
"couldn't find valid shortcut for bubble with shortcutId: " + shortcutId);
return false;
}
if (shortcutValid) {
return true;
}
// no log: canLaunch method has the failure log
return canLaunchInActivityView(mContext, metadata.getIntent(), pkg);
/**
* Whether an intent is properly configured to display in an {@link
* android.app.ActivityView} for bubbling.
*
* @param context the context to use.
* @param pendingIntent the pending intent of the bubble.
* @param packageName the notification package name for this bubble.
*/
// Keep checks in sync with BubbleController#canLaunchInActivityView.
@VisibleForTesting
protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
String packageName) {
if (pendingIntent == null) {
Slog.w(TAG, "Unable to create bubble -- no intent");
return false;
}
/**
* Whether an intent is properly configured to display in an {@link
* android.app.ActivityView}.
*
* @param context the context to use.
* @param pendingIntent the pending intent of the bubble.
* @param packageName the notification package name for this bubble.
*/
// Keep checks in sync with BubbleController#canLaunchInActivityView.
@VisibleForTesting
protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
String packageName) {
if (pendingIntent == null) {
Slog.w(TAG, "Unable to create bubble -- no intent");
return false;
}
Intent intent = pendingIntent.getIntent();
ActivityInfo info = intent != null
? intent.resolveActivityInfo(context.getPackageManager(), 0)
: null;
if (info == null) {
FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
packageName,
BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
Slog.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
+ intent);
return false;
}
if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
packageName,
BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
Slog.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
+ intent);
return false;
}
return true;
Intent intent = pendingIntent.getIntent();
ActivityInfo info = intent != null
? intent.resolveActivityInfo(context.getPackageManager(), 0)
: null;
if (info == null) {
FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
packageName,
BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
Slog.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
+ intent);
return false;
}
if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
packageName,
BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
Slog.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
+ intent);
return false;
}
return true;
}
private void logBubbleError(String key, String failureMessage) {
if (DBG) {
Slog.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
}
private void logBubbleError(String key, String failureMessage) {
if (DBG) {
Slog.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
}
}
}

View File

@@ -32,6 +32,7 @@ import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -3079,13 +3080,17 @@ public class NotificationManagerService extends SystemService {
return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
}
/**
* @return true if and only if "all" bubbles are allowed from the provided package.
*/
@Override
public boolean areBubblesAllowed(String pkg) {
return areBubblesAllowedForPackage(pkg, Binder.getCallingUid());
return getBubblePreferenceForPackage(pkg, Binder.getCallingUid())
== BUBBLE_PREFERENCE_ALL;
}
@Override
public boolean areBubblesAllowedForPackage(String pkg, int uid) {
public int getBubblePreferenceForPackage(String pkg, int uid) {
enforceSystemOrSystemUIOrSamePackage(pkg,
"Caller not system or systemui or same package");
@@ -3095,23 +3100,16 @@ public class NotificationManagerService extends SystemService {
"canNotifyAsPackage for uid " + uid);
}
return mPreferencesHelper.areBubblesAllowed(pkg, uid);
return mPreferencesHelper.getBubblePreference(pkg, uid);
}
@Override
public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
public void setBubblesAllowed(String pkg, int uid, int bubblePreference) {
enforceSystemOrSystemUI("Caller not system or systemui");
mPreferencesHelper.setBubblesAllowed(pkg, uid, allowed);
mPreferencesHelper.setBubblesAllowed(pkg, uid, bubblePreference);
handleSavePolicyFile();
}
@Override
public boolean hasUserApprovedBubblesForPackage(String pkg, int uid) {
enforceSystemOrSystemUI("Caller not system or systemui");
int lockedFields = mPreferencesHelper.getAppLockedFields(pkg, uid);
return (lockedFields & PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE) != 0;
}
@Override
public boolean shouldHideSilentStatusIcons(String callingPkg) {
checkCallerIsSameApp(callingPkg);
@@ -7210,7 +7208,10 @@ public class NotificationManagerService extends SystemService {
boolean interruptiveChanged =
record.canBubble() && (interruptiveBefore != record.isInterruptive());
changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
changed = indexChanged
|| interceptChanged
|| visibilityChanged
|| interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
buzzBeepBlinkLocked(record);

View File

@@ -17,6 +17,7 @@
package com.android.server.notification;
import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -117,10 +118,14 @@ public class PreferencesHelper implements RankingConfig {
@VisibleForTesting
static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
static final boolean DEFAULT_ALLOW_BUBBLE = true;
private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false;
static final boolean DEFAULT_GLOBAL_ALLOW_BUBBLE = true;
@VisibleForTesting
static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
/**
* Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
* fields.
@@ -148,7 +153,7 @@ public class PreferencesHelper implements RankingConfig {
private final NotificationChannelLogger mNotificationChannelLogger;
private SparseBooleanArray mBadgingEnabled;
private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE;
private boolean mBubblesEnabledGlobally = DEFAULT_GLOBAL_ALLOW_BUBBLE;
private boolean mAreChannelsBypassingDnd;
private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
@@ -226,8 +231,8 @@ public class PreferencesHelper implements RankingConfig {
parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
XmlUtils.readBooleanAttribute(
parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
XmlUtils.readBooleanAttribute(
parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
XmlUtils.readIntAttribute(
parser, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE));
r.importance = XmlUtils.readIntAttribute(
parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
r.priority = XmlUtils.readIntAttribute(
@@ -339,19 +344,19 @@ public class PreferencesHelper implements RankingConfig {
int uid) {
return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
DEFAULT_ALLOW_BUBBLE);
DEFAULT_BUBBLE_PREFERENCE);
}
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid) {
return getOrCreatePackagePreferencesLocked(pkg, userId, uid,
DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
DEFAULT_ALLOW_BUBBLE);
DEFAULT_BUBBLE_PREFERENCE);
}
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
boolean showBadge, boolean allowBubble) {
boolean showBadge, int bubblePreference) {
final String key = packagePreferencesKey(pkg, uid);
PackagePreferences
r = (uid == UNKNOWN_UID)
@@ -365,7 +370,7 @@ public class PreferencesHelper implements RankingConfig {
r.priority = priority;
r.visibility = visibility;
r.showBadge = showBadge;
r.allowBubble = allowBubble;
r.bubblePreference = bubblePreference;
try {
createDefaultChannelIfNeededLocked(r);
@@ -479,7 +484,7 @@ public class PreferencesHelper implements RankingConfig {
|| r.channels.size() > 0
|| r.groups.size() > 0
|| r.delegate != null
|| r.allowBubble != DEFAULT_ALLOW_BUBBLE;
|| r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -492,8 +497,8 @@ public class PreferencesHelper implements RankingConfig {
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
out.attribute(null, ATT_ALLOW_BUBBLE, Integer.toString(r.bubblePreference));
}
out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
@@ -544,14 +549,14 @@ public class PreferencesHelper implements RankingConfig {
*
* @param pkg the package to allow or not allow bubbles for.
* @param uid the uid to allow or not allow bubbles for.
* @param allowed whether bubbles are allowed.
* @param bubblePreference whether bubbles are allowed.
*/
public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
public void setBubblesAllowed(String pkg, int uid, int bubblePreference) {
boolean changed = false;
synchronized (mPackagePreferences) {
PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
changed = p.allowBubble != allowed;
p.allowBubble = allowed;
changed = p.bubblePreference != bubblePreference;
p.bubblePreference = bubblePreference;
p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
}
if (changed) {
@@ -567,9 +572,9 @@ public class PreferencesHelper implements RankingConfig {
* @return whether bubbles are allowed.
*/
@Override
public boolean areBubblesAllowed(String pkg, int uid) {
public int getBubblePreference(String pkg, int uid) {
synchronized (mPackagePreferences) {
return getOrCreatePackagePreferencesLocked(pkg, uid).allowBubble;
return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference;
}
}
@@ -788,6 +793,7 @@ public class PreferencesHelper implements RankingConfig {
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
channel.setAllowBubbles(existing != null && existing.canBubble());
}
clearLockedFieldsLocked(channel);
channel.setImportanceLockedByOEM(r.oemLockedImportance);
@@ -2125,7 +2131,7 @@ public class PreferencesHelper implements RankingConfig {
p.groups = new ArrayMap<>();
p.delegate = null;
p.lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
p.allowBubble = DEFAULT_ALLOW_BUBBLE;
p.bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
p.importance = DEFAULT_IMPORTANCE;
p.priority = DEFAULT_PRIORITY;
p.visibility = DEFAULT_VISIBILITY;
@@ -2165,15 +2171,15 @@ public class PreferencesHelper implements RankingConfig {
public void updateBubblesEnabled() {
final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_BUBBLES,
DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
if (newValue != mBubblesEnabled) {
mBubblesEnabled = newValue;
DEFAULT_GLOBAL_ALLOW_BUBBLE ? 1 : 0) == 1;
if (newValue != mBubblesEnabledGlobally) {
mBubblesEnabledGlobally = newValue;
updateConfig();
}
}
public boolean bubblesEnabled() {
return mBubblesEnabled;
return mBubblesEnabledGlobally;
}
public void updateBadgingEnabled() {
@@ -2229,7 +2235,7 @@ public class PreferencesHelper implements RankingConfig {
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
boolean showBadge = DEFAULT_SHOW_BADGE;
boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
int bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
// these fields are loaded on boot from a different source of truth and so are not
// written to notification policy xml

View File

@@ -29,7 +29,7 @@ public interface RankingConfig {
void setShowBadge(String packageName, int uid, boolean showBadge);
boolean canShowBadge(String packageName, int uid);
boolean badgingEnabled(UserHandle userHandle);
boolean areBubblesAllowed(String packageName, int uid);
int getBubblePreference(String packageName, int uid);
boolean bubblesEnabled();
boolean isGroupBlocked(String packageName, int uid, String groupId);

View File

@@ -1,261 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.notification;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BubbleCheckerTest extends UiServiceTestCase {
private static final String SHORTCUT_ID = "shortcut";
private static final String PKG = "pkg";
private static final String KEY = "key";
private static final int USER_ID = 1;
@Mock
ActivityManager mActivityManager;
@Mock
RankingConfig mRankingConfig;
@Mock
ShortcutHelper mShortcutHelper;
@Mock
NotificationRecord mNr;
@Mock
UserHandle mUserHandle;
@Mock
Notification mNotif;
@Mock
StatusBarNotification mSbn;
@Mock
NotificationChannel mChannel;
@Mock
Notification.BubbleMetadata mBubbleMetadata;
@Mock
PendingIntent mPendingIntent;
@Mock
Intent mIntent;
BubbleExtractor.BubbleChecker mBubbleChecker;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mNr.getKey()).thenReturn(KEY);
when(mNr.getSbn()).thenReturn(mSbn);
when(mNr.getUser()).thenReturn(mUserHandle);
when(mUserHandle.getIdentifier()).thenReturn(USER_ID);
when(mNr.getChannel()).thenReturn(mChannel);
when(mSbn.getPackageName()).thenReturn(PKG);
when(mSbn.getUser()).thenReturn(mUserHandle);
when(mNr.getNotification()).thenReturn(mNotif);
when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
mBubbleChecker = new BubbleExtractor.BubbleChecker(mContext,
mShortcutHelper,
mRankingConfig,
mActivityManager);
}
void setUpIntentBubble() {
when(mPendingIntent.getIntent()).thenReturn(mIntent);
when(mBubbleMetadata.getIntent()).thenReturn(mPendingIntent);
when(mBubbleMetadata.getShortcutId()).thenReturn(null);
}
void setUpShortcutBubble(boolean isValid) {
when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
ShortcutInfo info = mock(ShortcutInfo.class);
when(info.getId()).thenReturn(SHORTCUT_ID);
when(mShortcutHelper.getValidShortcutInfo(SHORTCUT_ID, PKG, mUserHandle))
.thenReturn(isValid ? info : null);
when(mBubbleMetadata.getIntent()).thenReturn(null);
}
void setUpBubblesEnabled(boolean feature, boolean app, boolean channel) {
when(mRankingConfig.bubblesEnabled()).thenReturn(feature);
when(mRankingConfig.areBubblesAllowed(PKG, USER_ID)).thenReturn(app);
when(mChannel.canBubble()).thenReturn(channel);
}
void setUpActivityIntent(boolean isResizable) {
when(mPendingIntent.getIntent()).thenReturn(mIntent);
ActivityInfo info = new ActivityInfo();
info.resizeMode = isResizable
? RESIZE_MODE_RESIZEABLE
: RESIZE_MODE_UNRESIZEABLE;
when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
}
//
// canBubble
//
@Test
public void testCanBubble_true_intentBubble() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
setUpIntentBubble();
setUpActivityIntent(true /* isResizable */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_true_shortcutBubble() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
setUpShortcutBubble(true /* isValid */);
assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_false_noIntentInvalidShortcut() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
setUpShortcutBubble(false /* isValid */);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_false_noIntentNoShortcut() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
when(mBubbleMetadata.getIntent()).thenReturn(null);
when(mBubbleMetadata.getShortcutId()).thenReturn(null);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubbble_false_noMetadata() {
setUpBubblesEnabled(true/* feature */, true /* app */, true /* channel */);
when(mNotif.getBubbleMetadata()).thenReturn(null);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_false_bubblesNotEnabled() {
setUpBubblesEnabled(false /* feature */, true /* app */, true /* channel */);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_false_packageNotAllowed() {
setUpBubblesEnabled(true /* feature */, false /* app */, true /* channel */);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
@Test
public void testCanBubble_false_channelNotAllowed() {
setUpBubblesEnabled(true /* feature */, true /* app */, false /* channel */);
assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
}
//
// canLaunchInActivityView
//
@Test
public void testCanLaunchInActivityView_true() {
setUpActivityIntent(true /* resizable */);
assertTrue(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
}
@Test
public void testCanLaunchInActivityView_false_noIntent() {
when(mPendingIntent.getIntent()).thenReturn(null);
assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
}
@Test
public void testCanLaunchInActivityView_false_noInfo() {
when(mPendingIntent.getIntent()).thenReturn(mIntent);
when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(null);
assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
}
@Test
public void testCanLaunchInActivityView_false_notResizable() {
setUpActivityIntent(false /* resizable */);
assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
}
//
// isNotificationAppropriateToBubble
//
@Test
public void testIsNotifAppropriateToBubble_true() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
setUpIntentBubble();
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpActivityIntent(true /* resizable */);
doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
assertTrue(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
}
@Test
public void testIsNotifAppropriateToBubble_false_lowRam() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
setUpActivityIntent(true /* resizable */);
doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
}
@Test
public void testIsNotifAppropriateToBubble_false_notMessageStyle() {
setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpActivityIntent(true /* resizable */);
doReturn(Notification.BigPictureStyle.class).when(mNotif).getNotificationStyle();
assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
}
}

View File

@@ -15,12 +15,19 @@
*/
package com.android.server.notification;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -28,6 +35,12 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ShortcutInfo;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
@@ -46,16 +59,32 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class BubbleExtractorTest extends UiServiceTestCase {
@Mock RankingConfig mConfig;
@Mock BubbleExtractor.BubbleChecker mBubbleChecker;
private static final String SHORTCUT_ID = "shortcut";
private static final String PKG = "com.android.server.notification";
private static final String TAG = null;
private static final int ID = 1001;
private static final int UID = 1000;
private static final int PID = 2000;
UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
BubbleExtractor mBubbleExtractor;
private String mPkg = "com.android.server.notification";
private int mId = 1001;
private String mTag = null;
private int mUid = 1000;
private int mPid = 2000;
private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
@Mock
RankingConfig mConfig;
@Mock
NotificationChannel mChannel;
@Mock
Notification.BubbleMetadata mBubbleMetadata;
@Mock
PendingIntent mPendingIntent;
@Mock
Intent mIntent;
@Mock
ShortcutInfo mShortcutInfo;
@Mock
ShortcutHelper mShortcutHelper;
@Mock
ActivityManager mActivityManager;
@Before
public void setUp() {
@@ -63,58 +92,103 @@ public class BubbleExtractorTest extends UiServiceTestCase {
mBubbleExtractor = new BubbleExtractor();
mBubbleExtractor.initialize(mContext, mock(NotificationUsageStats.class));
mBubbleExtractor.setConfig(mConfig);
mBubbleExtractor.setShortcutHelper(mock(ShortcutHelper.class));
mBubbleExtractor.setShortcutHelper(mShortcutHelper);
mBubbleExtractor.setActivityManager(mActivityManager);
when(mConfig.getNotificationChannel(PKG, UID, "a", false)).thenReturn(mChannel);
when(mShortcutInfo.getId()).thenReturn(SHORTCUT_ID);
}
private NotificationRecord getNotificationRecord(boolean allow, int importanceHigh) {
NotificationChannel channel = new NotificationChannel("a", "a", importanceHigh);
channel.setAllowBubbles(allow);
when(mConfig.getNotificationChannel(mPkg, mUid, "a", false)).thenReturn(channel);
/* NotificationRecord that fulfills conversation requirements (message style + shortcut) */
private NotificationRecord getNotificationRecord(boolean addBubble) {
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setPriority(Notification.PRIORITY_HIGH)
.setDefaults(Notification.DEFAULT_SOUND);
Person person = new Person.Builder()
.setName("bubblebot")
.build();
builder.setShortcutId(SHORTCUT_ID);
builder.setStyle(new Notification.MessagingStyle(person)
.setConversationTitle("Bubble Chat")
.addMessage("Hello?",
SystemClock.currentThreadTimeMillis() - 300000, person)
.addMessage("Is it me you're looking for?",
SystemClock.currentThreadTimeMillis(), person));
Notification n = builder.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
if (addBubble) {
n.setBubbleMetadata(mBubbleMetadata);
}
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, ID, TAG, UID,
PID, n, mUser, null, System.currentTimeMillis());
NotificationRecord r = new NotificationRecord(getContext(), sbn, mChannel);
r.setShortcutInfo(mShortcutInfo);
return r;
}
void setUpIntentBubble(boolean isValid) {
when(mPendingIntent.getIntent()).thenReturn(mIntent);
when(mBubbleMetadata.getIntent()).thenReturn(mPendingIntent);
when(mBubbleMetadata.getShortcutId()).thenReturn(null);
when(mPendingIntent.getIntent()).thenReturn(mIntent);
ActivityInfo info = new ActivityInfo();
info.resizeMode = isValid
? RESIZE_MODE_RESIZEABLE
: RESIZE_MODE_UNRESIZEABLE;
when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
}
void setUpShortcutBubble(boolean isValid) {
when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
when(mBubbleMetadata.getIntent()).thenReturn(null);
ShortcutInfo answer = isValid ? mShortcutInfo : null;
when(mShortcutHelper.getValidShortcutInfo(SHORTCUT_ID, PKG, mUser)).thenReturn(answer);
}
void setUpBubblesEnabled(boolean feature, int app, boolean channel) {
when(mConfig.bubblesEnabled()).thenReturn(feature);
when(mConfig.getBubblePreference(anyString(), anyInt())).thenReturn(app);
when(mChannel.canBubble()).thenReturn(channel);
}
//
// Tests
// Tests for the record being allowed to bubble.
//
@Test
public void testAppYesChannelNo() {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
false /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
when(mChannel.getUserLockedFields()).thenReturn(USER_LOCKED_ALLOW_BUBBLE);
mBubbleExtractor.process(r);
assertFalse(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testAppNoChannelYes() throws Exception {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_NONE /* app */,
true /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertFalse(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testAppYesChannelYes() {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -123,34 +197,85 @@ public class BubbleExtractorTest extends UiServiceTestCase {
@Test
public void testAppNoChannelNo() {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_NONE /* app */,
false /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertFalse(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testAppYesChannelYesUserNo() {
when(mConfig.bubblesEnabled()).thenReturn(false);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
setUpBubblesEnabled(false /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertFalse(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_true() {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
public void testAppSelectedChannelNo() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
false /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.setBubbleChecker(mBubbleChecker);
when(mBubbleChecker.isNotificationAppropriateToBubble(r)).thenReturn(true);
mBubbleExtractor.process(r);
assertFalse(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testAppSeletedChannelYes() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
true /* channel */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
when(mChannel.getUserLockedFields()).thenReturn(USER_LOCKED_ALLOW_BUBBLE);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
}
//
// Tests for flagging it as a bubble.
//
@Test
public void testFlagBubble_false_previouslyRemoved() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
NotificationRecord r = getNotificationRecord(true /* bubble */);
r.setFlagBubbleRemoved(true);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_true_shortcutBubble() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
@@ -158,14 +283,142 @@ public class BubbleExtractorTest extends UiServiceTestCase {
}
@Test
public void testFlagBubble_noFlag_previouslyRemoved() {
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
r.setFlagBubbleRemoved(true);
public void testFlagBubble_true_intentBubble() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpIntentBubble(true /* isValid */);
mBubbleExtractor.setBubbleChecker(mBubbleChecker);
when(mBubbleChecker.isNotificationAppropriateToBubble(r)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertTrue(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_noIntentInvalidShortcut() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpShortcutBubble(false /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
r.setShortcutInfo(null);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_invalidIntentNoShortcut() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpIntentBubble(false /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
r.setShortcutInfo(null);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_noIntentNoShortcut() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
// Shortcut here is for the notification not the bubble
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_noMetadata() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
NotificationRecord r = getNotificationRecord(false /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_notConversation() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(false);
setUpIntentBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
// No longer a conversation:
r.setShortcutInfo(null);
r.getNotification().extras.putString(Notification.EXTRA_TEMPLATE, null);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_lowRamDevice() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
setUpIntentBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_noIntent() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
setUpIntentBubble(true /* isValid */);
when(mPendingIntent.getIntent()).thenReturn(null);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
assertFalse(r.getNotification().isBubbleNotification());
}
@Test
public void testFlagBubble_false_noActivityInfo() {
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
setUpIntentBubble(true /* isValid */);
when(mPendingIntent.getIntent()).thenReturn(mIntent);
when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(null);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
assertTrue(r.canBubble());

View File

@@ -21,6 +21,10 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIB
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -39,6 +43,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -101,6 +106,7 @@ import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
@@ -204,7 +210,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
private TestableBubbleChecker mTestableBubbleChecker;
private ShortcutHelper mShortcutHelper;
@Mock
private IPackageManager mPackageManager;
@@ -347,21 +352,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
}
private class TestableBubbleChecker extends BubbleExtractor.BubbleChecker {
TestableBubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
ActivityManager manager) {
super(context, helper, config, manager);
}
@Override
protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
String packageName) {
// Tests for this not being true are in CTS NotificationManagerTest
return true;
}
}
private class TestableToastCallback extends ITransientNotification.Stub {
@Override
public void show(IBinder windowToken) {
@@ -480,9 +470,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Set the testable bubble extractor
RankingHelper rankingHelper = mService.getRankingHelper();
BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
mTestableBubbleChecker = new TestableBubbleChecker(mContext, mShortcutHelper,
mService.mPreferencesHelper, mActivityManager);
extractor.setBubbleChecker(mTestableBubbleChecker);
extractor.setActivityManager(mActivityManager);
// Tests call directly into the Binder.
mBinderService = mService.getBinderService();
@@ -544,13 +532,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
boolean pkgEnabled, boolean channelEnabled) {
int pkgPref, boolean channelEnabled) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
mService.mPreferencesHelper.updateBubblesEnabled();
assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
try {
mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
mBinderService.setBubblesAllowed(pkg, uid, pkgPref);
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -687,19 +675,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
false);
}
private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() {
PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
return new Notification.BubbleMetadata.Builder(pi,
Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
}
private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
String groupKey, boolean isSummary) {
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
.build();
// It needs remote input to be bubble-able
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
@@ -724,11 +705,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
nb.setGroup(groupKey);
}
if (addBubbleMetadata) {
nb.setBubbleMetadata(getBubbleMetadataBuilder().build());
nb.setBubbleMetadata(getBubbleMetadata());
}
return nb;
}
private Notification.BubbleMetadata getBubbleMetadata() {
PendingIntent pendingIntent = mock(PendingIntent.class);
Intent intent = mock(Intent.class);
when(pendingIntent.getIntent()).thenReturn(intent);
ActivityInfo info = new ActivityInfo();
info.resizeMode = RESIZE_MODE_RESIZEABLE;
when(intent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
return new Notification.BubbleMetadata.Builder(
pendingIntent,
Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon))
.build();
}
private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
throws RemoteException {
@@ -4483,24 +4479,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testBubble() throws Exception {
mBinderService.setBubblesAllowed(PKG, mUid, false);
assertFalse(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
mBinderService.setBubblesAllowed(PKG, mUid, BUBBLE_PREFERENCE_NONE);
assertFalse(mBinderService.areBubblesAllowed(PKG));
assertEquals(mBinderService.getBubblePreferenceForPackage(PKG, mUid),
BUBBLE_PREFERENCE_NONE);
}
@Test
public void testUserApprovedBubblesForPackage() throws Exception {
assertFalse(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
mBinderService.setBubblesAllowed(PKG, mUid, true);
assertTrue(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
assertTrue(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
public void testUserApprovedBubblesForPackageSelected() throws Exception {
mBinderService.setBubblesAllowed(PKG, mUid, BUBBLE_PREFERENCE_SELECTED);
assertEquals(mBinderService.getBubblePreferenceForPackage(PKG, mUid),
BUBBLE_PREFERENCE_SELECTED);
}
@Test
public void testUserApprovedBubblesForPackageAll() throws Exception {
mBinderService.setBubblesAllowed(PKG, mUid, BUBBLE_PREFERENCE_ALL);
assertTrue(mBinderService.areBubblesAllowed(PKG));
assertEquals(mBinderService.getBubblePreferenceForPackage(PKG, mUid),
BUBBLE_PREFERENCE_ALL);
}
@Test
public void testUserRejectsBubblesForPackage() throws Exception {
assertFalse(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
mBinderService.setBubblesAllowed(PKG, mUid, false);
assertTrue(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
assertFalse(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
mBinderService.setBubblesAllowed(PKG, mUid, BUBBLE_PREFERENCE_NONE);
assertFalse(mBinderService.areBubblesAllowed(PKG));
}
@Test
@@ -5166,8 +5169,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubble() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nr =
generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -5185,8 +5190,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_NONE /* app */,
true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubble_noFlag_appNotAllowed");
@@ -5204,15 +5211,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif with bubble metadata but not our other misc requirements
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setBubbleMetadata(getBubbleMetadataBuilder().build());
.setBubbleMetadata(getBubbleMetadata());
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -5232,8 +5241,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_flag_messaging");
@@ -5249,8 +5260,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
// Bubbles are NOT allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_NONE /* app */,
true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -5267,8 +5280,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5291,11 +5306,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
// Bubbles are allowed except on this channel
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
false /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
nr.getChannel().lockFields(USER_LOCKED_ALLOW_BUBBLE);
// Post the notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
@@ -5488,7 +5506,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testAreBubblesAllowedForPackage_crossUser() throws Exception {
try {
mBinderService.areBubblesAllowedForPackage(mContext.getPackageName(),
mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(),
mUid + UserHandle.PER_USER_RANGE);
fail("Cannot call cross user without permission");
} catch (SecurityException e) {
@@ -5497,7 +5515,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// cross user, with permission, no problem
enableInteractAcrossUsers();
mBinderService.areBubblesAllowedForPackage(mContext.getPackageName(),
mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(),
mUid + UserHandle.PER_USER_RANGE);
}
@@ -5508,8 +5526,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbleChanged_false() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5539,8 +5559,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbleChanged_true() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5576,8 +5598,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5605,8 +5629,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbleIsFlagRemoved_resetOnUpdate() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbleIsFlagRemoved_resetOnUpdate");
@@ -5637,8 +5664,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbleIsFlagRemoved_resetOnBubbleChangedTrue() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbleIsFlagRemoved_trueOnBubbleChangedTrue");
@@ -5666,16 +5696,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testOnBubbleNotificationSuppressionChanged() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// Bubble notification
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, nr.getSbn().getUserId(), true /* global */,
true /* app */, true /* channel */);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
@@ -5888,8 +5916,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// And we are low ram
when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5972,8 +6002,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -6002,8 +6034,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -6032,8 +6066,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_flagRemoved_whenShortcutRemoved()
throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
ArgumentCaptor.forClass(LauncherApps.Callback.class);
@@ -6090,8 +6126,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_shortcut_stopListeningWhenNotifRemoved()
throws RemoteException {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
ArgumentCaptor.forClass(LauncherApps.Callback.class);
@@ -6141,8 +6179,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -6165,8 +6205,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -6197,8 +6239,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_bubbleStays_whenClicked()
throws Exception {
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
// GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
mService.addNotification(nr);
@@ -6332,7 +6378,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
assertEquals(original.canBubble(), friendChannel.canBubble());
assertFalse(friendChannel.canBubble()); // can't be modified by app
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}

View File

@@ -16,6 +16,9 @@
package com.android.server.notification;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -23,6 +26,7 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
import static com.google.common.truth.Truth.assertThat;
@@ -1094,7 +1098,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
.getUserLockedFields());
final NotificationChannel update = getChannel();
update.setAllowBubbles(false);
update.setAllowBubbles(true);
mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true);
assertEquals(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE,
mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update.getId(), false)
@@ -1734,14 +1738,14 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, pkgPair);
mHelper.setNotificationDelegate(PKG_O, UID_O, "", 1);
mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
mHelper.setBubblesAllowed(PKG_O, UID_O, false);
mHelper.setBubblesAllowed(PKG_O, UID_O, DEFAULT_BUBBLE_PREFERENCE);
mHelper.setShowBadge(PKG_O, UID_O, false);
mHelper.setAppImportanceLocked(PKG_O, UID_O);
mHelper.clearData(PKG_O, UID_O);
assertEquals(IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_O, UID_O));
assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), DEFAULT_BUBBLE_PREFERENCE);
assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
@@ -2412,21 +2416,21 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
public void testAllowBubbles_defaults() throws Exception {
assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
public void testBubblePreference_defaults() throws Exception {
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger);
loadStreamXml(baos, false, UserHandle.USER_ALL);
assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE);
assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
}
@Test
public void testAllowBubbles_xml() throws Exception {
mHelper.setBubblesAllowed(PKG_O, UID_O, false);
assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
public void testBubblePreference_xml() throws Exception {
mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_NONE);
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE);
assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
mHelper.getAppLockedFields(PKG_O, UID_O));
@@ -2434,7 +2438,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger);
loadStreamXml(baos, false, UserHandle.USER_ALL);
assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE);
assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
mHelper.getAppLockedFields(PKG_O, UID_O));
}
@@ -2766,9 +2770,29 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
public void testSetBubblesAllowed_false() {
mHelper.setBubblesAllowed(PKG_O, UID_O, false);
assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
public void testSetBubblesAllowed_none() {
// Change it to non-default first
mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_ALL);
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_ALL);
verify(mHandler, times(1)).requestSort();
reset(mHandler);
// Now test
mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_NONE);
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE);
verify(mHandler, times(1)).requestSort();
}
@Test
public void testSetBubblesAllowed_all() {
mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_ALL);
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_ALL);
verify(mHandler, times(1)).requestSort();
}
@Test
public void testSetBubblesAllowed_selected() {
mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_SELECTED);
assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_SELECTED);
verify(mHandler, times(1)).requestSort();
}