diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2931f33e4cc46..fce74496d9c4c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3203,6 +3203,14 @@ public class Notification implements Parcelable
return mBubbleMetadata;
}
+ /**
+ * Sets the {@link BubbleMetadata} for this notification.
+ * @hide
+ */
+ public void setBubbleMetadata(BubbleMetadata data) {
+ mBubbleMetadata = data;
+ }
+
/**
* Returns whether the platform is allowed (by the app developer) to generate contextual actions
* for this notification.
diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml
new file mode 100644
index 0000000000000..1947f58f8f5e6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 87de9d4d3b51c..964a59170d353 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -219,6 +219,58 @@ asked for it -->
android:gravity="center"
android:orientation="vertical">
+
+
+
+
+
+
+
+
Alerting
+
+ Bubble
+
Helps you focus without sound or vibration.
Gets your attention with sound or vibration.
+
+ Keeps your attention with a floating shortcut to this content.
+
These notifications can\'t be modified.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index f90e561dece1c..f67cd1bef281d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -319,7 +319,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
packageName,
row.getEntry().getChannel(),
row.getUniqueChannels(),
- sbn,
+ row.getEntry(),
mCheckSaveListener,
onSettingsClick,
onAppSettingsClick,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 148d83b5ab5c9..a9a4804a2be47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -65,7 +65,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubbleExperimentConfig;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import java.lang.annotation.Retention;
@@ -99,6 +102,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
// standard controls
private static final int ACTION_ALERT = 5;
+ private TextView mBubbleDescriptionView;
private TextView mPriorityDescriptionView;
private TextView mSilentDescriptionView;
@@ -116,6 +120,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private Set mUniqueChannelsInRow;
private NotificationChannel mSingleNotificationChannel;
private int mStartingChannelImportance;
+ private boolean mStartedAsBubble;
private boolean mWasShownHighPriority;
private boolean mPressedApply;
private boolean mPresentingChannelEditorDialog = false;
@@ -125,8 +130,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
* level; non-null once the user takes an action which indicates an explicit preference.
*/
@Nullable private Integer mChosenImportance;
+ /**
+ * The last bubble setting chosen by the user. Null if the user has not chosen a bubble level;
+ * non-null once the user takes an action which indicates an explicit preference.
+ */
+ @Nullable private Boolean mChosenBubbleEnabled;
private boolean mIsSingleDefaultChannel;
private boolean mIsNonblockable;
+ private boolean mIsBubbleable;
+ private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private AnimatorSet mExpandAnimation;
private boolean mIsDeviceProvisioned;
@@ -137,18 +149,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private NotificationGuts mGutsContainer;
private Drawable mPkgIcon;
+ private BubbleController mBubbleController;
+
/** Whether this view is being shown as part of the blocking helper. */
private boolean mIsForBlockingHelper;
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
/**
* String that describes how the user exit or quit out of this view, also used as a counter tag.
*/
private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
+
// used by standard ui
private OnClickListener mOnAlert = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
mChosenImportance = IMPORTANCE_DEFAULT;
+ if (mStartedAsBubble) {
+ mChosenBubbleEnabled = false;
+ }
applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */);
};
@@ -156,9 +177,19 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private OnClickListener mOnSilent = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
mChosenImportance = IMPORTANCE_LOW;
+ if (mStartedAsBubble) {
+ mChosenBubbleEnabled = false;
+ }
applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */);
};
+ /** Used by standard ui (in an experiment) {@see BubbleExperimentConfig#allowNotifBubbleMenu} */
+ private OnClickListener mOnBubble = v -> {
+ mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
+ mChosenBubbleEnabled = true;
+ applyAlertingBehavior(BEHAVIOR_BUBBLE, true /* userTriggered */);
+ };
+
// used by standard ui
private OnClickListener mOnDismissSettings = v -> {
mPressedApply = true;
@@ -224,6 +255,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
protected void onFinishInflate() {
super.onFinishInflate();
+ mBubbleDescriptionView = findViewById(R.id.bubble_summary);
mPriorityDescriptionView = findViewById(R.id.alert_summary);
mSilentDescriptionView = findViewById(R.id.silence_summary);
}
@@ -251,7 +283,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
final String pkg,
final NotificationChannel notificationChannel,
final Set uniqueChannelsInRow,
- final StatusBarNotification sbn,
+ final NotificationEntry entry,
final CheckSaveListener checkSaveListener,
final OnSettingsClickListener onSettingsClick,
final OnAppSettingsClickListener onAppSettingsClick,
@@ -261,7 +293,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
boolean wasShownHighPriority)
throws RemoteException {
bindNotification(pm, iNotificationManager, visualStabilityManager, pkg, notificationChannel,
- uniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
+ uniqueChannelsInRow, entry, checkSaveListener, onSettingsClick,
onAppSettingsClick, isDeviceProvisioned, isNonblockable,
false /* isBlockingHelper */,
importance, wasShownHighPriority);
@@ -274,7 +306,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
String pkg,
NotificationChannel notificationChannel,
Set uniqueChannelsInRow,
- StatusBarNotification sbn,
+ NotificationEntry entry,
CheckSaveListener checkSaveListener,
OnSettingsClickListener onSettingsClick,
OnAppSettingsClickListener onAppSettingsClick,
@@ -288,10 +320,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mMetricsLogger = Dependency.get(MetricsLogger.class);
mVisualStabilityManager = visualStabilityManager;
mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class);
+ mBubbleController = Dependency.get(BubbleController.class);
mPackageName = pkg;
mUniqueChannelsInRow = uniqueChannelsInRow;
mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
- mSbn = sbn;
+ mEntry = entry;
+ mSbn = entry.getSbn();
mPm = pm;
mAppSettingsClickListener = onAppSettingsClick;
mAppName = mPackageName;
@@ -318,6 +352,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
&& numTotalChannels == 1;
}
+ mIsBubbleable = mEntry.getBubbleMetadata() != null;
+ mStartedAsBubble = mEntry.isBubble();
+
bindHeader();
bindChannelDetails();
@@ -365,6 +402,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
findViewById(R.id.non_configurable_text).setVisibility(GONE);
findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
+ findViewById(R.id.bubble).setVisibility(mIsBubbleable ? VISIBLE : GONE);
}
View turnOffButton = findViewById(R.id.turn_off_notifications);
@@ -378,12 +416,17 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
View silent = findViewById(R.id.silence);
View alert = findViewById(R.id.alert);
+ View bubble = findViewById(R.id.bubble);
silent.setOnClickListener(mOnSilent);
alert.setOnClickListener(mOnAlert);
+ bubble.setOnClickListener(mOnBubble);
- applyAlertingBehavior(
- mWasShownHighPriority ? BEHAVIOR_ALERTING : BEHAVIOR_SILENT,
- false /* userTriggered */);
+ int behavior = mStartedAsBubble
+ ? BEHAVIOR_BUBBLE
+ : mWasShownHighPriority
+ ? BEHAVIOR_ALERTING
+ : BEHAVIOR_SILENT;
+ applyAlertingBehavior(behavior, false /* userTriggered */);
}
private void bindHeader() {
@@ -544,6 +587,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
+ if (mChosenBubbleEnabled != null && mStartedAsBubble != mChosenBubbleEnabled) {
+ if (mChosenBubbleEnabled) {
+ mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+ } else {
+ mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+ }
+ }
+
Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
bgHandler.post(
new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
@@ -553,6 +604,16 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
private void applyAlertingBehavior(@AlertingBehavior int behavior, boolean userTriggered) {
if (userTriggered) {
TransitionSet transition = new TransitionSet();
@@ -569,6 +630,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
TransitionManager.beginDelayedTransition(this, transition);
}
+ View bubble = findViewById(R.id.bubble);
View alert = findViewById(R.id.alert);
View silence = findViewById(R.id.silence);
@@ -576,33 +638,53 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
case BEHAVIOR_ALERTING:
mPriorityDescriptionView.setVisibility(VISIBLE);
mSilentDescriptionView.setVisibility(GONE);
+ mBubbleDescriptionView.setVisibility(GONE);
post(() -> {
alert.setSelected(true);
silence.setSelected(false);
+ bubble.setSelected(false);
});
break;
- case BEHAVIOR_SILENT:
+ case BEHAVIOR_SILENT:
mSilentDescriptionView.setVisibility(VISIBLE);
mPriorityDescriptionView.setVisibility(GONE);
+ mBubbleDescriptionView.setVisibility(GONE);
post(() -> {
alert.setSelected(false);
silence.setSelected(true);
+ bubble.setSelected(false);
});
break;
+
+ case BEHAVIOR_BUBBLE:
+ mBubbleDescriptionView.setVisibility(VISIBLE);
+ mSilentDescriptionView.setVisibility(GONE);
+ mPriorityDescriptionView.setVisibility(GONE);
+ post(() -> {
+ alert.setSelected(false);
+ silence.setSelected(false);
+ bubble.setSelected(true);
+ });
+ break;
+
default:
throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior);
}
boolean isAChange = mWasShownHighPriority != (behavior == BEHAVIOR_ALERTING);
+ boolean isABubbleChange = mStartedAsBubble != (behavior == BEHAVIOR_BUBBLE);
TextView done = findViewById(R.id.done);
- done.setText(isAChange ? R.string.inline_ok_button : R.string.inline_done_button);
+ done.setText((isAChange || isABubbleChange)
+ ? R.string.inline_ok_button
+ : R.string.inline_done_button);
}
private void saveImportanceAndExitReason(@NotificationInfoAction int action) {
switch (action) {
case ACTION_UNDO:
mChosenImportance = mStartingChannelImportance;
+ mChosenBubbleEnabled = mStartedAsBubble;
break;
case ACTION_DELIVER_SILENTLY:
mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
@@ -685,6 +767,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
if (mChosenImportance != null) {
mStartingChannelImportance = mChosenImportance;
}
+ if (mChosenBubbleEnabled != null) {
+ mStartedAsBubble = mChosenBubbleEnabled;
+ }
mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
if (mIsForBlockingHelper) {
@@ -884,8 +969,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
@Retention(SOURCE)
- @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT})
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
private @interface AlertingBehavior {}
private static final int BEHAVIOR_ALERTING = 0;
private static final int BEHAVIOR_SILENT = 1;
+ private static final int BEHAVIOR_BUBBLE = 2;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
index 9bc962c77019e..fe117fe443a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
@@ -33,6 +33,7 @@ public class SbnBuilder {
private int mUid;
private int mInitialPid;
private Notification mNotification = new Notification();
+ private Notification.BubbleMetadata mBubbleMetadata;
private UserHandle mUser = UserHandle.of(0);
private String mOverrideGroupKey;
private long mPostTime;
@@ -54,6 +55,9 @@ public class SbnBuilder {
}
public StatusBarNotification build() {
+ if (mBubbleMetadata != null) {
+ mNotification.setBubbleMetadata(mBubbleMetadata);
+ }
return new StatusBarNotification(
mPkg,
mOpPkg,
@@ -116,4 +120,9 @@ public class SbnBuilder {
mPostTime = postTime;
return this;
}
+
+ public SbnBuilder setBubbleMetadata(Notification.BubbleMetadata data) {
+ mBubbleMetadata = data;
+ return this;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index bc616c5d71630..0b123fc8ff7e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -321,6 +321,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
.build();
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+ NotificationEntry entry = row.getEntry();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
@@ -331,7 +332,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(statusBarNotification.getPackageName()),
any(NotificationChannel.class),
anySet(),
- eq(statusBarNotification),
+ eq(entry),
any(NotificationInfo.CheckSaveListener.class),
any(NotificationInfo.OnSettingsClickListener.class),
any(NotificationInfo.OnAppSettingsClickListener.class),
@@ -352,6 +353,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
.build();
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+ NotificationEntry entry = row.getEntry();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
@@ -362,7 +364,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(statusBarNotification.getPackageName()),
any(NotificationChannel.class),
anySet(),
- eq(statusBarNotification),
+ eq(entry),
any(NotificationInfo.CheckSaveListener.class),
any(NotificationInfo.OnSettingsClickListener.class),
any(NotificationInfo.OnAppSettingsClickListener.class),
@@ -385,6 +387,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
row.getEntry().setIsHighPriority(true);
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+ NotificationEntry entry = row.getEntry();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
@@ -395,7 +398,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(statusBarNotification.getPackageName()),
any(NotificationChannel.class),
anySet(),
- eq(statusBarNotification),
+ eq(entry),
any(NotificationInfo.CheckSaveListener.class),
any(NotificationInfo.OnSettingsClickListener.class),
any(NotificationInfo.OnAppSettingsClickListener.class),
@@ -416,6 +419,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
.build();
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+ NotificationEntry entry = row.getEntry();
+
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
@@ -427,7 +432,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(statusBarNotification.getPackageName()),
any(NotificationChannel.class),
anySet(),
- eq(statusBarNotification),
+ eq(entry),
any(NotificationInfo.CheckSaveListener.class),
any(NotificationInfo.OnSettingsClickListener.class),
any(NotificationInfo.OnAppSettingsClickListener.class),
@@ -448,6 +453,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
.build();
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
+ NotificationEntry entry = row.getEntry();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
@@ -458,7 +464,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(statusBarNotification.getPackageName()),
any(NotificationChannel.class),
anySet(),
- eq(statusBarNotification),
+ eq(entry),
any(NotificationInfo.CheckSaveListener.class),
any(NotificationInfo.OnSettingsClickListener.class),
any(NotificationInfo.OnAppSettingsClickListener.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 703adf7a047f5..bdca7efeb6080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -49,10 +50,13 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
@@ -72,7 +76,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.After;
import org.junit.Before;
@@ -106,6 +115,9 @@ public class NotificationInfoTest extends SysuiTestCase {
private Set mNotificationChannelSet = new HashSet<>();
private Set mDefaultNotificationChannelSet = new HashSet<>();
private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mBubbleSbn;
+ private NotificationEntry mBubbleEntry;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -119,6 +131,8 @@ public class NotificationInfoTest extends SysuiTestCase {
private NotificationBlockingHelperManager mBlockingHelperManager;
@Mock
private VisualStabilityManager mVisualStabilityManager;
+ @Mock
+ private BubbleController mBubbleController;
@Before
public void setUp() throws Exception {
@@ -126,13 +140,18 @@ public class NotificationInfoTest extends SysuiTestCase {
NotificationBlockingHelperManager.class,
mBlockingHelperManager);
mTestableLooper = TestableLooper.get(this);
+
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mDependency.injectTestDependency(BubbleController.class, mBubbleController);
// Inflate the layout
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
null);
mNotificationInfo.setGutsParent(mock(NotificationGuts.class));
+ // Our view is never attached to a window so the View#post methods in NotificationInfo never
+ // get called. Setting this will skip the post and do the action immediately.
+ mNotificationInfo.mSkipPost = true;
// PackageManager must return a packageInfo and applicationInfo.
final PackageInfo packageInfo = new PackageInfo();
@@ -164,6 +183,16 @@ public class NotificationInfoTest extends SysuiTestCase {
mDefaultNotificationChannelSet.add(mDefaultNotificationChannel);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, BubblesTestActivity.class), 0);
+ mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+ new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+ .build();
+ mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
Settings.Secure.putInt(mContext.getContentResolver(),
NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
@@ -182,17 +211,6 @@ public class NotificationInfoTest extends SysuiTestCase {
() -> VISIBLE == mNotificationInfo.findViewById(R.id.confirmation).getVisibility());
}
- private void ensureNoUndoButton() {
- PollingCheck.waitFor(1000,
- () -> GONE == mNotificationInfo.findViewById(R.id.confirmation).getVisibility()
- && !mNotificationInfo.isAnimating());
- }
-
- private void waitForStopButton() {
- PollingCheck.waitFor(1000,
- () -> VISIBLE == mNotificationInfo.findViewById(R.id.prompt).getVisibility());
- }
-
@Test
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
@@ -203,7 +221,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -228,7 +246,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -249,7 +267,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -273,6 +291,7 @@ public class NotificationInfoTest extends SysuiTestCase {
applicationInfo);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
mNotificationInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
@@ -280,7 +299,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ entry,
null,
null,
null,
@@ -304,7 +323,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -331,7 +350,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -353,7 +372,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -374,7 +393,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mDefaultNotificationChannel,
mDefaultNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -399,7 +418,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mDefaultNotificationChannel,
mDefaultNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -420,7 +439,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -441,7 +460,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
mock(NotificationInfo.OnSettingsClickListener.class),
null,
@@ -468,7 +487,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
@@ -495,7 +514,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -517,7 +536,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
@@ -540,7 +559,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -555,7 +574,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
(View v, NotificationChannel c, int appUid) -> { },
null,
@@ -576,7 +595,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -600,7 +619,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -625,7 +644,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -647,7 +666,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mVisualStabilityManager,
TEST_PACKAGE_NAME, mNotificationChannel,
createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
- mSbn,
+ mEntry,
null,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(null, c);
@@ -675,7 +694,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
- mSbn,
+ mEntry,
null,
null,
null,
@@ -698,7 +717,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
- mSbn,
+ mEntry,
null,
null,
null,
@@ -721,7 +740,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -737,6 +756,202 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility());
}
+ @Test
+ public void testBindNotification_alertIsSelected() throws Exception {
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+ assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected());
+ }
+
+ @Test
+ public void testBindNotification_silenceIsSelected() throws Exception {
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ false);
+ assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected());
+ }
+
+ @Test
+ public void testBindNotification_bubbleIsSelected() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ assertTrue(bubbleView.isSelected());
+ }
+
+ @Test
+ public void testBindNotification_whenCanBubble() throws Exception {
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ assertFalse(bubbleView.isSelected());
+ }
+
+ @Test
+ public void testBindNotification_whenCantBubble() throws Exception {
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.GONE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testBubble_promotesBubble() throws Exception {
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+
+ assertFalse(mBubbleEntry.isBubble());
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mNotificationInfo.findViewById(R.id.done).performClick();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+ }
+
+ @Test
+ public void testAlert_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.alert).performClick();
+ mNotificationInfo.findViewById(R.id.done).performClick();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ }
+
+ @Test
+ public void testSilence_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true,
+ false,
+ IMPORTANCE_DEFAULT,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.silence).performClick();
+ mNotificationInfo.findViewById(R.id.done).performClick();
+ mNotificationInfo.handleCloseControls(true, false);
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ }
+
@Test
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationInfo.bindNotification(
@@ -746,7 +961,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -769,7 +984,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -795,7 +1010,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -821,7 +1036,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -848,7 +1063,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -878,7 +1093,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel /* notificationChannel */,
createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
- mSbn,
+ mEntry,
listener /* checkSaveListener */,
null /* onSettingsClick */,
null /* onAppSettingsClick */,
@@ -917,7 +1132,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel /* notificationChannel */,
createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
- mSbn,
+ mEntry,
listener /* checkSaveListener */,
null /* onSettingsClick */,
null /* onAppSettingsClick */,
@@ -945,7 +1160,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel /* notificationChannel */,
createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
- mSbn,
+ mEntry,
listener /* checkSaveListener */,
null /* onSettingsClick */,
null /* onAppSettingsClick */,
@@ -970,7 +1185,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet /* numChannels */,
- mSbn,
+ mEntry,
null /* checkSaveListener */,
null /* onSettingsClick */,
null /* onAppSettingsClick */,
@@ -999,7 +1214,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet /* numChannels */,
- mSbn,
+ mEntry,
null /* checkSaveListener */,
null /* onSettingsClick */,
null /* onAppSettingsClick */,
@@ -1033,7 +1248,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1064,7 +1279,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1094,7 +1309,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1127,7 +1342,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1161,7 +1376,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1195,7 +1410,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1232,7 +1447,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1268,7 +1483,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1295,7 +1510,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1325,7 +1540,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1358,7 +1573,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
null,
null,
null,
@@ -1386,7 +1601,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
(Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
},
@@ -1421,7 +1636,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
(Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
},
@@ -1449,7 +1664,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
- mSbn,
+ mEntry,
(Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
},