Make bubble settings a pref with an int rather than a bool am: a92268cd01 am: 82b58a59a9
Change-Id: I70f06051cd13f8cdbf4ee3eef2035d2448ab4ca7
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -7214,7 +7212,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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user