diff options
9 files changed, 485 insertions, 74 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2931f33e4cc4..fce74496d9c4 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3204,6 +3204,14 @@ public class Notification implements Parcelable } /** + * 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 000000000000..1947f58f8f5e --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,3c-4.97,0 -9,4.03 -9,9c0,1.39 0.32,2.69 0.88,3.86l1.53,-1.53C5.15,13.6 5,12.82 5,12c0,-3.86 3.14,-7 7,-7s7,3.14 7,7s-3.14,7 -7,7c-0.83,0 -1.62,-0.15 -2.35,-0.42l-1.53,1.53C9.3,20.67 10.61,21 12,21c4.97,0 9,-4.03 9,-9C21,7.03 16.97,3 12,3z" + android:fillColor="#000000"/> + <path + android:pathData="M12.99,15.99l2,0l0,-7l-7,0l0,2l3.59,0l-8.79,8.8l1.41,1.41l8.79,-8.79z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 87de9d4d3b51..964a59170d35 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -220,6 +220,58 @@ asked for it --> android:orientation="vertical"> <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/bubble" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/notification_importance_button_padding" + android:layout_marginBottom="@dimen/notification_importance_button_separation" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/bubble_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_create_bubble" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/bubble_label" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_bubble_title"/> + </LinearLayout> + <TextView + android:id="@+id/bubble_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_bubble" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout android:id="@+id/alert" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 008294980d94..19daa9039f55 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1717,12 +1717,18 @@ <!-- [CHAR LIMIT=100] Notification Importance title --> <string name="notification_alert_title">Alerting</string> + <!-- [CHAR LIMIT=100] Notification Importance title --> + <string name="notification_bubble_title">Bubble</string> + <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary --> <string name="notification_channel_summary_low">Helps you focus without sound or vibration.</string> <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary --> <string name="notification_channel_summary_default">Gets your attention with sound or vibration.</string> + <!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary --> + <string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string> + <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. --> <string name="notification_unblockable_desc">These notifications can\'t be modified.</string> 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 f90e561dece1..f67cd1bef281 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 148d83b5ab5c..a9a4804a2be4 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<NotificationChannel> 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<NotificationChannel> 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<NotificationChannel> 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 9bc962c77019..fe117fe443a6 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 bc616c5d7163..0b123fc8ff7e 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 703adf7a047f..bdca7efeb608 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<NotificationChannel> mNotificationChannelSet = new HashSet<>(); private Set<NotificationChannel> 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, @@ -738,6 +757,202 @@ public class NotificationInfoTest extends SysuiTestCase { } @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( mMockPackageManager, @@ -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(); }, |