From 9abc50646f785b413df887bdc115b2b37ba34f1e Mon Sep 17 00:00:00 2001 From: Gus Prevas Date: Wed, 31 Oct 2018 16:11:04 -0400 Subject: [PATCH] Adds silence toggle to notification settings. This change modifies the UI that appears when a notification is long-pressed to include a third option when the notification could potentially buzz or make sound, allowing the user to promote or demote the notification's channel such that it will not alert (or will start alerting) for future notifications. Test: atest SystemUITests Bug: 116622974 Change-Id: Ia070d0ef1b181b4de8b3d49ace9ff2a65fed8deb --- .../NotificationListenerService.java | 29 +- .../NotificationRankingUpdate.java | 10 +- .../SystemUI/res/layout/notification_info.xml | 7 + packages/SystemUI/res/values/strings.xml | 12 + .../notification/NotificationData.java | 4 + .../logging/NotificationCounters.java | 3 + .../notification/row/NotificationGuts.java | 18 +- .../row/NotificationGutsManager.java | 9 +- .../notification/row/NotificationInfo.java | 147 ++++++-- .../notification/NotificationDataTest.java | 9 +- .../NotificationEntryManagerTest.java | 4 +- .../row/NotificationGutsManagerTest.java | 92 ++++- .../row/NotificationInfoTest.java | 351 +++++++++++++++--- .../NotificationManagerService.java | 4 +- .../NotificationListenerServiceTest.java | 8 +- 15 files changed, 590 insertions(+), 117 deletions(-) diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 56e6aea815ecd..64eae0cf4635d 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1462,6 +1462,7 @@ public abstract class NotificationListenerService extends Service { private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL; private boolean mHidden; private boolean mAudiblyAlerted; + private boolean mNoisy; private ArrayList mSmartActions; private ArrayList mSmartReplies; @@ -1636,6 +1637,11 @@ public abstract class NotificationListenerService extends Service { return mAudiblyAlerted; } + /** @hide */ + public boolean isNoisy() { + return mNoisy; + } + /** * @hide */ @@ -1646,7 +1652,8 @@ public abstract class NotificationListenerService extends Service { NotificationChannel channel, ArrayList overridePeople, ArrayList snoozeCriteria, boolean showBadge, int userSentiment, boolean hidden, boolean audiblyAlerted, - ArrayList smartActions, ArrayList smartReplies) { + boolean noisy, ArrayList smartActions, + ArrayList smartReplies) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -1663,6 +1670,7 @@ public abstract class NotificationListenerService extends Service { mUserSentiment = userSentiment; mHidden = hidden; mAudiblyAlerted = audiblyAlerted; + mNoisy = noisy; mSmartActions = smartActions; mSmartReplies = smartReplies; } @@ -1715,6 +1723,7 @@ public abstract class NotificationListenerService extends Service { private ArrayMap mUserSentiment; private ArrayMap mHidden; private ArrayMap mAudiblyAlerted; + private ArrayMap mNoisy; private ArrayMap> mSmartActions; private ArrayMap> mSmartReplies; @@ -1746,7 +1755,8 @@ public abstract class NotificationListenerService extends Service { getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key), getChannel(key), getOverridePeople(key), getSnoozeCriteria(key), getShowBadge(key), getUserSentiment(key), getHidden(key), - getAudiblyAlerted(key), getSmartActions(key), getSmartReplies(key)); + getAudiblyAlerted(key), getNoisy(key), getSmartActions(key), + getSmartReplies(key)); return rank >= 0; } @@ -1894,6 +1904,16 @@ public abstract class NotificationListenerService extends Service { return audiblyAlerted == null ? false : audiblyAlerted.booleanValue(); } + private boolean getNoisy(String key) { + synchronized (this) { + if (mNoisy == null) { + buildNoisyLocked(); + } + } + Boolean noisy = mNoisy.get(key); + return noisy == null ? false : noisy.booleanValue(); + } + private ArrayList getSmartActions(String key) { synchronized (this) { if (mSmartActions == null) { @@ -2033,6 +2053,11 @@ public abstract class NotificationListenerService extends Service { mAudiblyAlerted = buildBooleanMapFromBundle(mRankingUpdate.getAudiblyAlerted()); } + // Locked by 'this' + private void buildNoisyLocked() { + mNoisy = buildBooleanMapFromBundle(mRankingUpdate.getNoisy()); + } + // Locked by 'this' private void buildSmartActions() { Bundle smartActions = mRankingUpdate.getSmartActions(); diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index b561bfd98038e..f80df93364f40 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -40,13 +40,14 @@ public class NotificationRankingUpdate implements Parcelable { private final Bundle mSmartActions; private final Bundle mSmartReplies; private final Bundle mAudiblyAlerted; + private final Bundle mNoisy; public NotificationRankingUpdate(String[] keys, String[] interceptedKeys, Bundle visibilityOverrides, Bundle suppressedVisualEffects, int[] importance, Bundle explanation, Bundle overrideGroupKeys, Bundle channels, Bundle overridePeople, Bundle snoozeCriteria, Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions, - Bundle smartReplies, Bundle audiblyAlerted) { + Bundle smartReplies, Bundle audiblyAlerted, Bundle noisy) { mKeys = keys; mInterceptedKeys = interceptedKeys; mVisibilityOverrides = visibilityOverrides; @@ -63,6 +64,7 @@ public class NotificationRankingUpdate implements Parcelable { mSmartActions = smartActions; mSmartReplies = smartReplies; mAudiblyAlerted = audiblyAlerted; + mNoisy = noisy; } public NotificationRankingUpdate(Parcel in) { @@ -83,6 +85,7 @@ public class NotificationRankingUpdate implements Parcelable { mSmartActions = in.readBundle(); mSmartReplies = in.readBundle(); mAudiblyAlerted = in.readBundle(); + mNoisy = in.readBundle(); } @Override @@ -108,6 +111,7 @@ public class NotificationRankingUpdate implements Parcelable { out.writeBundle(mSmartActions); out.writeBundle(mSmartReplies); out.writeBundle(mAudiblyAlerted); + out.writeBundle(mNoisy); } public static final Parcelable.Creator CREATOR @@ -184,4 +188,8 @@ public class NotificationRankingUpdate implements Parcelable { public Bundle getAudiblyAlerted() { return mAudiblyAlerted; } + + public Bundle getNoisy() { + return mNoisy; + } } diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index f138685e98105..c86ebe70db5c4 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -151,6 +151,13 @@ android:layout_height="match_parent" android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" style="@style/TextAppearance.NotificationInfo.Button" /> + These notifications will be minimized + + These notifications will be shown silently + + + These notifications will alert you + You usually dismiss these notifications. \nKeep showing them? @@ -1556,6 +1562,12 @@ Minimize + + Show silently + + + Show and alert + Keep showing notifications from this app? diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index b84b77c6d1050..f7c07f370cb1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -102,6 +102,8 @@ public class NotificationData { public StatusBarNotification notification; public NotificationChannel channel; public boolean audiblyAlerted; + public boolean noisy; + public int importance; public StatusBarIconView icon; public StatusBarIconView expandedIcon; public ExpandableNotificationRow row; // the outer expanded view @@ -155,6 +157,8 @@ public class NotificationData { public void populateFromRanking(@NonNull Ranking ranking) { channel = ranking.getChannel(); audiblyAlerted = ranking.audiblyAlerted(); + noisy = ranking.isNoisy(); + importance = ranking.getImportance(); snoozeCriteria = ranking.getSnoozeCriteria(); userSentiment = ranking.getUserSentiment(); smartActions = ranking.getSmartActions() == null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationCounters.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationCounters.java index 28c07a34beed1..43b5503682cc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationCounters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationCounters.java @@ -30,6 +30,9 @@ public class NotificationCounters { /** Counter tag for when the user hits 'stop notifications' in the blocking helper. */ public static final String BLOCKING_HELPER_STOP_NOTIFICATIONS = "blocking_helper_stop_notifications"; + /** Counter tag for when the user hits 'show silently' in the blocking helper. */ + public static final String BLOCKING_HELPER_TOGGLE_SILENT = + "blocking_helper_toggle_silent"; /** Counter tag for when the user hits 'keep showing' in the blocking helper. */ public static final String BLOCKING_HELPER_KEEP_SHOWING = "blocking_helper_keep_showing"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java index 0a197daf91864..fbe9c5d40beb5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java @@ -23,7 +23,6 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Handler; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -31,6 +30,8 @@ import android.view.ViewAnimationUtils; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; @@ -98,6 +99,11 @@ public class NotificationGuts extends FrameLayout { * Return whether something changed and needs to be saved, possibly requiring a bouncer. */ boolean shouldBeSaved(); + + /** + * Called when the guts view has finished its close animation. + */ + default void onFinishedClosing() {} } public interface OnGutsClosedListener { @@ -304,7 +310,7 @@ public class NotificationGuts extends FrameLayout { x, y, r, 0); a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - a.addListener(new AnimateCloseListener(this /* view */)); + a.addListener(new AnimateCloseListener(this /* view */, mGutsContent)); a.start(); } else { // Fade in the blocking helper. @@ -312,11 +318,12 @@ public class NotificationGuts extends FrameLayout { .alpha(0f) .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE) .setInterpolator(Interpolators.ALPHA_OUT) - .setListener(new AnimateCloseListener(this /* view */)) + .setListener(new AnimateCloseListener(this, /* view */mGutsContent)) .start(); } } else { Log.w(TAG, "Failed to animate guts close"); + mGutsContent.onFinishedClosing(); } } @@ -414,15 +421,18 @@ public class NotificationGuts extends FrameLayout { /** Listener for animations executed in {@link #animateClose(int, int, boolean)}. */ private static class AnimateCloseListener extends AnimatorListenerAdapter { final View mView; + private final GutsContent mGutsContent; - private AnimateCloseListener(View view) { + private AnimateCloseListener(View view, GutsContent gutsContent) { mView = view; + mGutsContent = gutsContent; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mView.setVisibility(View.GONE); + mGutsContent.onFinishedClosing(); } } } 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 24999525ab728..b838c9b5482d7 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 @@ -28,7 +28,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; @@ -46,15 +45,13 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; @@ -298,7 +295,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mDeviceProvisionedController.isDeviceProvisioned(), row.getIsNonblockable(), isForBlockingHelper, - row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE); + row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE, + row.getEntry().noisy, + row.getEntry().importance); } 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 903c27277b709..522da4d514705 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 @@ -16,13 +16,18 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.INotificationManager; import android.app.Notification; @@ -54,6 +59,7 @@ 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.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import java.util.List; @@ -65,6 +71,17 @@ import java.util.List; public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent { private static final String TAG = "InfoGuts"; + @IntDef(prefix = { "SWAP_CONTENT_" }, value = { + SWAP_CONTENT_UNDO, + SWAP_CONTENT_TOGGLE_SILENT, + SWAP_CONTENT_BLOCK, + }) + @interface SwapContentAction {} + + private static final int SWAP_CONTENT_UNDO = 0; + private static final int SWAP_CONTENT_TOGGLE_SILENT = 1; + private static final int SWAP_CONTENT_BLOCK = 2; + private INotificationManager mINotificationManager; private PackageManager mPm; private MetricsLogger mMetricsLogger; @@ -74,7 +91,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private int mAppUid; private int mNumUniqueChannelsInRow; private NotificationChannel mSingleNotificationChannel; - private int mStartingUserImportance; + private int mStartingChannelImportance; + private int mStartingChannelOrNotificationImportance; private int mChosenImportance; private boolean mIsSingleDefaultChannel; private boolean mIsNonblockable; @@ -82,6 +100,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private AnimatorSet mExpandAnimation; private boolean mIsForeground; private boolean mIsDeviceProvisioned; + private boolean mIsNoisy; private CheckSaveListener mCheckSaveListener; private OnSettingsClickListener mOnSettingsClickListener; @@ -102,10 +121,22 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G closeControls(v); }; + private OnClickListener mOnToggleSilent = v -> { + Runnable saveImportance = () -> { + mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT; + swapContent(SWAP_CONTENT_TOGGLE_SILENT); + }; + if (mCheckSaveListener != null) { + mCheckSaveListener.checkSave(saveImportance, mSbn); + } else { + saveImportance.run(); + } + }; + private OnClickListener mOnStopOrMinimizeNotifications = v -> { Runnable saveImportance = () -> { mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS; - swapContent(false); + swapContent(SWAP_CONTENT_BLOCK); }; if (mCheckSaveListener != null) { mCheckSaveListener.checkSave(saveImportance, mSbn); @@ -118,7 +149,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G // Reset exit counter that we'll log and record an undo event separately (not an exit event) mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO); - swapContent(true); + swapContent(SWAP_CONTENT_UNDO); }; public NotificationInfo(Context context, AttributeSet attrs) { @@ -152,12 +183,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G final OnSettingsClickListener onSettingsClick, final OnAppSettingsClickListener onAppSettingsClick, boolean isDeviceProvisioned, - boolean isNonblockable) + boolean isNonblockable, + boolean isNoisy, + int importance) throws RemoteException { bindNotification(pm, iNotificationManager, pkg, notificationChannel, numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick, onAppSettingsClick, isDeviceProvisioned, isNonblockable, - false /* isBlockingHelper */, false /* isUserSentimentNegative */); + false /* isBlockingHelper */, false /* isUserSentimentNegative */, isNoisy, + importance); } public void bindNotification( @@ -173,7 +207,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isDeviceProvisioned, boolean isNonblockable, boolean isForBlockingHelper, - boolean isUserSentimentNegative) + boolean isUserSentimentNegative, + boolean isNoisy, + int importance) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -186,7 +222,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mCheckSaveListener = checkSaveListener; mOnSettingsClickListener = onSettingsClick; mSingleNotificationChannel = notificationChannel; - mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance(); + int channelImportance = mSingleNotificationChannel.getImportance(); + mStartingChannelImportance = mChosenImportance = channelImportance; + mStartingChannelOrNotificationImportance = + channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance; mNegativeUserSentiment = isUserSentimentNegative; mIsNonblockable = isNonblockable; mIsForeground = @@ -194,6 +233,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mIsForBlockingHelper = isForBlockingHelper; mAppUid = mSbn.getUid(); mIsDeviceProvisioned = isDeviceProvisioned; + mIsNoisy = isNoisy; int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); @@ -306,7 +346,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } private boolean hasImportanceChanged() { - return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance; + return mSingleNotificationChannel != null + && mStartingChannelImportance != mChosenImportance; } private void saveImportance() { @@ -320,34 +361,46 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G */ private void updateImportance() { MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE, - mChosenImportance - mStartingUserImportance); + mChosenImportance - mStartingChannelImportance); Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, - mStartingUserImportance, mChosenImportance)); + mStartingChannelImportance, mChosenImportance)); } private void bindButtons() { // Set up stay-in-notification actions View block = findViewById(R.id.block); TextView keep = findViewById(R.id.keep); + TextView silent = findViewById(R.id.toggle_silent); View minimize = findViewById(R.id.minimize); findViewById(R.id.undo).setOnClickListener(mOnUndo); block.setOnClickListener(mOnStopOrMinimizeNotifications); keep.setOnClickListener(mOnKeepShowing); + silent.setOnClickListener(mOnToggleSilent); minimize.setOnClickListener(mOnStopOrMinimizeNotifications); if (mIsNonblockable) { keep.setText(android.R.string.ok); block.setVisibility(GONE); + silent.setVisibility(GONE); minimize.setVisibility(GONE); } else if (mIsForeground) { block.setVisibility(GONE); + silent.setVisibility(GONE); minimize.setVisibility(VISIBLE); - } else if (!mIsForeground) { + } else { block.setVisibility(VISIBLE); + boolean showToggleSilent = mIsNoisy + && NotificationUtils.useNewInterruptionModel(mContext); + silent.setVisibility(showToggleSilent ? VISIBLE : GONE); + boolean isCurrentlyAlerting = + mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT; + silent.setText(isCurrentlyAlerting + ? R.string.inline_silent_button_silent + : R.string.inline_silent_button_alert); minimize.setVisibility(GONE); } @@ -368,7 +421,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private void swapContent(boolean showPrompt) { + private void swapContent(@SwapContentAction int action) { if (mExpandAnimation != null) { mExpandAnimation.cancel(); } @@ -378,26 +431,43 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G TextView confirmationText = findViewById(R.id.confirmation_text); View header = findViewById(R.id.header); - if (showPrompt) { - mChosenImportance = mStartingUserImportance; - } else if (mIsForeground) { - mChosenImportance = IMPORTANCE_MIN; - confirmationText.setText(R.string.notification_channel_minimized); - } else { - mChosenImportance = IMPORTANCE_NONE; - confirmationText.setText(R.string.notification_channel_disabled); + switch (action) { + case SWAP_CONTENT_UNDO: + mChosenImportance = mStartingChannelImportance; + break; + case SWAP_CONTENT_TOGGLE_SILENT: + if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) { + mChosenImportance = IMPORTANCE_LOW; + confirmationText.setText(R.string.notification_channel_silenced); + } else { + mChosenImportance = IMPORTANCE_HIGH; + confirmationText.setText(R.string.notification_channel_unsilenced); + } + break; + case SWAP_CONTENT_BLOCK: + if (mIsForeground) { + mChosenImportance = IMPORTANCE_MIN; + confirmationText.setText(R.string.notification_channel_minimized); + } else { + mChosenImportance = IMPORTANCE_NONE; + confirmationText.setText(R.string.notification_channel_disabled); + } + break; + default: + throw new IllegalArgumentException(); } + boolean isUndo = action == SWAP_CONTENT_UNDO; ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA, - prompt.getAlpha(), showPrompt ? 1f : 0f); - promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT); + prompt.getAlpha(), isUndo ? 1f : 0f); + promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT); ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA, - confirmation.getAlpha(), showPrompt ? 0f : 1f); - confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN); + confirmation.getAlpha(), isUndo ? 0f : 1f); + confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN); - prompt.setVisibility(showPrompt ? VISIBLE : GONE); - confirmation.setVisibility(showPrompt ? GONE : VISIBLE); - header.setVisibility(showPrompt ? VISIBLE : GONE); + prompt.setVisibility(isUndo ? VISIBLE : GONE); + confirmation.setVisibility(isUndo ? GONE : VISIBLE); + header.setVisibility(isUndo ? VISIBLE : GONE); mExpandAnimation = new AnimatorSet(); mExpandAnimation.playTogether(promptAnim, confirmAnim); @@ -413,8 +483,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public void onAnimationEnd(Animator animation) { if (!cancelled) { - prompt.setVisibility(showPrompt ? VISIBLE : GONE); - confirmation.setVisibility(showPrompt ? GONE : VISIBLE); + prompt.setVisibility(isUndo ? VISIBLE : GONE); + confirmation.setVisibility(isUndo ? GONE : VISIBLE); } } }); @@ -427,6 +497,25 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } + @Override + public void onFinishedClosing() { + mStartingChannelImportance = mChosenImportance; + if (mChosenImportance != IMPORTANCE_UNSPECIFIED) { + mStartingChannelOrNotificationImportance = mChosenImportance; + } + mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; + + View prompt = findViewById(R.id.prompt); + ViewGroup confirmation = findViewById(R.id.confirmation); + View header = findViewById(R.id.header); + prompt.setVisibility(VISIBLE); + prompt.setAlpha(1f); + confirmation.setVisibility(GONE); + confirmation.setAlpha(1f); + header.setVisibility(VISIBLE); + header.setAlpha(1f); + } + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java index 0251f64fd6dfa..8e6bfe3a5f916 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java @@ -437,15 +437,15 @@ public class NotificationDataTest extends SysuiTestCase { outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, outRanking.canShowBadge(), outRanking.getUserSentiment(), true, - false, null, null); + false, false, null, null); } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) { outRanking.populate(key, outRanking.getRank(), outRanking.matchesInterruptionFilter(), outRanking.getVisibilityOverride(), 255, outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null, - outRanking.canShowBadge(), outRanking.getUserSentiment(), true, false, null, - null); + outRanking.canShowBadge(), outRanking.getUserSentiment(), true, false, + false, null, null); } else { outRanking.populate(key, outRanking.getRank(), outRanking.matchesInterruptionFilter(), @@ -453,8 +453,7 @@ public class NotificationDataTest extends SysuiTestCase { outRanking.getImportance(), outRanking.getImportanceExplanation(), outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null, outRanking.canShowBadge(), outRanking.getUserSentiment(), false, false, - null, - null); + false, null, null); } return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 9c68e7d9ef3a8..9f8a5cc0afdfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -167,7 +167,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { 0, NotificationManager.IMPORTANCE_DEFAULT, null, null, - null, null, null, true, sentiment, false, false, null, null); + null, null, null, true, sentiment, false, false, false, null, null); return true; }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); } @@ -186,7 +186,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { null, null, null, null, null, true, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, false, - smartActions, null); + false, smartActions, null); return true; }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); } 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 ee35449e05daa..626726d4aa4fc 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 @@ -19,8 +19,10 @@ package com.android.systemui.statusbar.notification.row; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.service.notification.NotificationListenerService.Ranking + .USER_SENTIMENT_NEGATIVE; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @@ -34,15 +36,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; -import android.app.NotificationManager; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; @@ -57,11 +58,12 @@ import android.view.View; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationTestHelper; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; +import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager + .OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -71,8 +73,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Tests for {@link NotificationGutsManager}. @@ -84,7 +86,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private NotificationChannel mTestNotificationChannel = new NotificationChannel( - TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); private TestableLooper mTestableLooper; private Handler mHandler; private NotificationTestHelper mHelper; @@ -297,7 +299,9 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(true) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */); + eq(true) /* isUserSentimentNegative */, + eq(false) /*isNoisy */, + eq(0)); } @Test @@ -324,7 +328,69 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(false) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */); + eq(true) /* isUserSentimentNegative */, + eq(false) /*isNoisy */, + eq(0)); + } + + @Test + public void testInitializeNotificationInfoView_noisy() throws Exception { + NotificationInfo notificationInfoView = mock(NotificationInfo.class); + ExpandableNotificationRow row = spy(mHelper.createRow()); + row.setBlockingHelperShowing(true); + row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + row.getEntry().noisy = true; + when(row.getIsNonblockable()).thenReturn(false); + StatusBarNotification statusBarNotification = row.getStatusBarNotification(); + + mGutsManager.initializeNotificationInfo(row, notificationInfoView); + + verify(notificationInfoView).bindNotification( + any(PackageManager.class), + any(INotificationManager.class), + eq(statusBarNotification.getPackageName()), + any(NotificationChannel.class), + anyInt(), + eq(statusBarNotification), + any(NotificationInfo.CheckSaveListener.class), + any(NotificationInfo.OnSettingsClickListener.class), + any(NotificationInfo.OnAppSettingsClickListener.class), + eq(false), + eq(false), + eq(true) /* isForBlockingHelper */, + eq(true) /* isUserSentimentNegative */, + eq(true) /*isNoisy */, + eq(0)); + } + + @Test + public void testInitializeNotificationInfoView_importance() throws Exception { + NotificationInfo notificationInfoView = mock(NotificationInfo.class); + ExpandableNotificationRow row = spy(mHelper.createRow()); + row.setBlockingHelperShowing(true); + row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + row.getEntry().importance = IMPORTANCE_DEFAULT; + when(row.getIsNonblockable()).thenReturn(false); + StatusBarNotification statusBarNotification = row.getStatusBarNotification(); + + mGutsManager.initializeNotificationInfo(row, notificationInfoView); + + verify(notificationInfoView).bindNotification( + any(PackageManager.class), + any(INotificationManager.class), + eq(statusBarNotification.getPackageName()), + any(NotificationChannel.class), + anyInt(), + eq(statusBarNotification), + any(NotificationInfo.CheckSaveListener.class), + any(NotificationInfo.OnSettingsClickListener.class), + any(NotificationInfo.OnAppSettingsClickListener.class), + eq(false), + eq(false), + eq(true) /* isForBlockingHelper */, + eq(true) /* isUserSentimentNegative */, + eq(false) /*isNoisy */, + eq(IMPORTANCE_DEFAULT)); } @Test @@ -352,7 +418,9 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true), eq(false), eq(false) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */); + eq(true) /* isUserSentimentNegative */, + eq(false) /*isNoisy */, + eq(0)); } @Test 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 ca968a8af85bc..37441963e32db 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 @@ -17,11 +17,14 @@ package com.android.systemui.statusbar.notification.row; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME; +import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -56,6 +59,7 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.IBinder; import android.os.UserHandle; +import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -72,6 +76,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -150,6 +155,15 @@ public class NotificationInfoTest extends SysuiTestCase { IMPORTANCE_LOW); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); + + Settings.Secure.putInt(mContext.getContentResolver(), + NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); + } + + @After + public void tearDown() { + Settings.Secure.putInt(mContext.getContentResolver(), + NOTIFICATION_NEW_INTERRUPTION_MODEL, 0); } // TODO: if tests are taking too long replace this with something that makes the animation @@ -172,7 +186,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsTextApplicationName() throws Exception { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final TextView textView = mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -184,7 +199,8 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -192,7 +208,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider); @@ -208,7 +225,8 @@ public class NotificationInfoTest extends SysuiTestCase { eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID))) .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -219,7 +237,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -228,7 +247,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false); + false, false, IMPORTANCE_DEFAULT); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -241,7 +260,7 @@ public class NotificationInfoTest extends SysuiTestCase { eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true, - false); + false, false, IMPORTANCE_DEFAULT); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -249,7 +268,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -257,18 +277,71 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_BlockButton() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final View block = mNotificationInfo.findViewById(R.id.block); + final View toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent); final View minimize = mNotificationInfo.findViewById(R.id.minimize); assertEquals(VISIBLE, block.getVisibility()); + assertEquals(GONE, toggleSilent.getVisibility()); assertEquals(GONE, minimize.getVisibility()); } + @Test + public void testBindNotification_SilenceButton() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent); + assertEquals(VISIBLE, toggleSilent.getVisibility()); + assertEquals( + mContext.getString(R.string.inline_silent_button_silent), toggleSilent.getText()); + } + + @Test + public void testBindNotification_UnSilenceButton() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_LOW); + final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent); + assertEquals(VISIBLE, toggleSilent.getVisibility()); + assertEquals( + mContext.getString(R.string.inline_silent_button_alert), toggleSilent.getText()); + } + + @Test + public void testBindNotification_SilenceButton_ChannelImportanceUnspecified() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent); + assertEquals(VISIBLE, toggleSilent.getVisibility()); + assertEquals( + mContext.getString(R.string.inline_silent_button_silent), toggleSilent.getText()); + } + + @Test + public void testBindNotification_UnSilenceButton_ChannelImportanceUnspecified() + throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_LOW); + final TextView toggleSilent = mNotificationInfo.findViewById(R.id.toggle_silent); + assertEquals(VISIBLE, toggleSilent.getVisibility()); + assertEquals( + mContext.getString(R.string.inline_silent_button_alert), toggleSilent.getText()); + } + @Test public void testBindNotification_MinButton() throws Exception { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final View block = mNotificationInfo.findViewById(R.id.block); final View minimize = mNotificationInfo.findViewById(R.id.minimize); assertEquals(GONE, block.getVisibility()); @@ -283,7 +356,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); latch.countDown(); - }, null, true, false); + }, null, true, false, false, IMPORTANCE_DEFAULT); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -294,7 +367,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -306,7 +380,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); - }, null, false, false); + }, null, false, false, false, IMPORTANCE_DEFAULT); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -314,11 +388,12 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, (View v, NotificationChannel c, int appUid) -> { - }, null, true, false); + }, null, true, false, false, IMPORTANCE_DEFAULT); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -326,7 +401,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger, times(0)).count(anyString(), anyInt()); } @@ -335,7 +411,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true, - true, true); + true, true, false, IMPORTANCE_DEFAULT); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger, times(1)).count(anyString(), anyInt()); } @@ -348,7 +424,7 @@ public class NotificationInfoTest extends SysuiTestCase { (View v, NotificationChannel c, int appUid) -> { assertEquals(null, c); latch.countDown(); - }, null, true, true); + }, null, true, true, false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.info).performClick(); // Verify that listener was triggered. @@ -361,7 +437,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true); + null, true, true, false, IMPORTANCE_DEFAULT); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -372,7 +448,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true); + null, true, true, false, IMPORTANCE_DEFAULT); final TextView blockView = mNotificationInfo.findViewById(R.id.block); assertEquals(GONE, blockView.getVisibility()); } @@ -381,7 +457,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_BlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false, - true, true); + true, true, false, IMPORTANCE_DEFAULT); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText()); @@ -390,7 +466,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -400,7 +477,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -410,7 +488,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -423,7 +502,8 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); mTestableLooper.processAllMessages(); @@ -431,12 +511,41 @@ public class NotificationInfoTest extends SysuiTestCase { anyString(), eq(TEST_UID), any()); } + @Test + public void testDoesNotUpdateNotificationChannelAfterImportanceChangedSilenced() + throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + mTestableLooper.processAllMessages(); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), any()); + } + + @Test + public void testDoesNotUpdateNotificationChannelAfterImportanceChangedUnSilenced() + throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + mTestableLooper.processAllMessages(); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), any()); + } + @Test public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception { int originalImportance = mNotificationChannel.getImportance(); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -450,7 +559,8 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.handleCloseControls(true, false); @@ -468,7 +578,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , - true, false /* isNonblockable */); + true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -489,7 +599,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , - true, false /* isNonblockable */); + true, false /* isNonblockable */, false, /* isNoisy */IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -510,7 +620,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */); + true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -538,7 +648,7 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */); + true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -566,7 +676,8 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */ , false /* isNonblockable */, true /* isForBlockingHelper */, - true, true /* isUserSentimentNegative */); + true, true /* isUserSentimentNegative */, false, /* isNoisy */ + IMPORTANCE_DEFAULT); mNotificationInfo.handleCloseControls(true /* save */, false /* force */); @@ -585,7 +696,7 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */); + true /* isUserSentimentNegative */, false, /* isNoisy */IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -607,7 +718,8 @@ public class NotificationInfoTest extends SysuiTestCase { false /* isNonblockable */, true /* isForBlockingHelper */, true, - false /* isUserSentimentNegative */); + false /* isUserSentimentNegative */, + false, /* isNoisy */IMPORTANCE_DEFAULT); NotificationGuts guts = mock(NotificationGuts.class); doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean()); mNotificationInfo.setGutsParent(guts); @@ -621,7 +733,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -634,7 +747,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBlockChangedCallsUpdateNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -666,7 +780,8 @@ public class NotificationInfoTest extends SysuiTestCase { true /*provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */); + true /* isUserSentimentNegative */, + false, /* isNoisy */IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -687,7 +802,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testNonBlockableAppDoesNotBecomeMin() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -701,7 +817,8 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -721,7 +838,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testKeepUpdatesNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.handleCloseControls(true, false); @@ -738,7 +856,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBlockUndoDoesNotBlockNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -759,7 +878,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testMinUndoDoesNotMinNotificationChannel() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -776,11 +896,98 @@ public class NotificationInfoTest extends SysuiTestCase { assertEquals(IMPORTANCE_LOW, mNotificationChannel.getImportance()); } + @Test + public void testSilenceCallsUpdateNotificationChannel() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + mNotificationInfo.handleCloseControls(true, false); + + mTestableLooper.processAllMessages(); + ArgumentCaptor updated = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), updated.capture()); + assertTrue((updated.getValue().getUserLockedFields() + & USER_LOCKED_IMPORTANCE) != 0); + assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); + } + + @Test + public void testUnSilenceCallsUpdateNotificationChannel() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + mNotificationInfo.handleCloseControls(true, false); + + mTestableLooper.processAllMessages(); + ArgumentCaptor updated = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), updated.capture()); + assertTrue((updated.getValue().getUserLockedFields() + & USER_LOCKED_IMPORTANCE) != 0); + assertEquals(IMPORTANCE_HIGH, updated.getValue().getImportance()); + } + + @Test + public void testSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() + throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + mNotificationInfo.handleCloseControls(true, false); + + mTestableLooper.processAllMessages(); + ArgumentCaptor updated = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), updated.capture()); + assertTrue((updated.getValue().getUserLockedFields() + & USER_LOCKED_IMPORTANCE) != 0); + assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); + } + + @Test + public void testUnSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified() + throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + false, IMPORTANCE_LOW); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + mNotificationInfo.handleCloseControls(true, false); + + mTestableLooper.processAllMessages(); + ArgumentCaptor updated = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), eq(TEST_UID), updated.capture()); + assertTrue((updated.getValue().getUserLockedFields() + & USER_LOCKED_IMPORTANCE) != 0); + assertEquals(IMPORTANCE_HIGH, updated.getValue().getImportance()); + } + @Test public void testCloseControlsDoesNotUpdateiMinIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -795,7 +1002,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -812,7 +1020,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { - }, null, null, true, true); + }, null, null, true, true, false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -830,7 +1038,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { saveImportance.run(); - }, null, null, true, false); + }, null, null, true, false, false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); mTestableLooper.processAllMessages(); @@ -866,7 +1074,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, (View v, Intent intent) -> { latch.countDown(); - }, true, false); + }, true, false, false, IMPORTANCE_DEFAULT); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(View.VISIBLE, settingsLink.getVisibility()); settingsLink.performClick(); @@ -894,7 +1102,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null, (View v, Intent intent) -> { latch.countDown(); - }, true, false); + }, true, false, false, IMPORTANCE_DEFAULT); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(View.VISIBLE, settingsLink.getVisibility()); settingsLink.performClick(); @@ -913,7 +1121,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, sbn, null, null, - null, true, false); + null, true, false, false, IMPORTANCE_DEFAULT); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(GONE, settingsLink.getVisibility()); } @@ -933,7 +1141,8 @@ public class NotificationInfoTest extends SysuiTestCase { 0, null, 0, 0, n, UserHandle.CURRENT, null, 0); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, true, false); + TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, true, false, + false, IMPORTANCE_DEFAULT); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(GONE, settingsLink.getVisibility()); } @@ -956,7 +1165,7 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, sbn, null, null, null, false, true, - true, true); + true, true, false, IMPORTANCE_DEFAULT); final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings); assertEquals(GONE, settingsLink.getVisibility()); } @@ -972,7 +1181,8 @@ public class NotificationInfoTest extends SysuiTestCase { mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.minimize).performClick(); waitForUndoButton(); @@ -984,7 +1194,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testUndoText_block() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -992,11 +1203,40 @@ public class NotificationInfoTest extends SysuiTestCase { assertTrue(confirmationText.getText().toString().contains("won't see")); } + @Test + public void testUndoText_silence() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + TextView confirmationText = mNotificationInfo.findViewById(R.id.confirmation_text); + assertEquals(mContext.getString(R.string.notification_channel_silenced), + confirmationText.getText()); + } + + @Test + public void testUndoText_unsilence() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + true, IMPORTANCE_DEFAULT); + + mNotificationInfo.findViewById(R.id.toggle_silent).performClick(); + waitForUndoButton(); + TextView confirmationText = mNotificationInfo.findViewById(R.id.confirmation_text); + assertEquals(mContext.getString(R.string.notification_channel_unsilenced), + confirmationText.getText()); + } + @Test public void testNoHeaderOnConfirmation() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); @@ -1007,7 +1247,8 @@ public class NotificationInfoTest extends SysuiTestCase { public void testHeaderOnUndo() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, + false, IMPORTANCE_DEFAULT); mNotificationInfo.findViewById(R.id.block).performClick(); waitForUndoButton(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f87a5f7a1a521..b404c41c211dd 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6564,6 +6564,7 @@ public class NotificationManagerService extends SystemService { Bundle smartActions = new Bundle(); Bundle smartReplies = new Bundle(); Bundle audiblyAlerted = new Bundle(); + Bundle noisy = new Bundle(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); if (!isVisibleToListener(record.sbn, info)) { @@ -6594,6 +6595,7 @@ public class NotificationManagerService extends SystemService { smartActions.putParcelableArrayList(key, record.getSmartActions()); smartReplies.putCharSequenceArrayList(key, record.getSmartReplies()); audiblyAlerted.putBoolean(key, record.getAudiblyAlerted()); + noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null); } final int M = keys.size(); String[] keysAr = keys.toArray(new String[M]); @@ -6605,7 +6607,7 @@ public class NotificationManagerService extends SystemService { return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides, suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys, channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden, - smartActions, smartReplies, audiblyAlerted); + smartActions, smartReplies, audiblyAlerted, noisy); } boolean hasCompanionDevice(ManagedServiceInfo info) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 0d7b5843f47e9..bcba15df8756d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -114,6 +114,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { Bundle smartActions = new Bundle(); Bundle smartReplies = new Bundle(); Bundle audiblyAlerted = new Bundle(); + Bundle noisy = new Bundle(); for (int i = 0; i < mKeys.length; i++) { String key = mKeys[i]; @@ -134,12 +135,13 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { smartActions.putParcelableArrayList(key, getSmartActions(key, i)); smartReplies.putCharSequenceArrayList(key, getSmartReplies(key, i)); audiblyAlerted.putBoolean(key, audiblyAlerted(i)); + noisy.putBoolean(key, getNoisy(i)); } NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys, interceptedKeys.toArray(new String[0]), visibilityOverrides, suppressedVisualEffects, importance, explanation, overrideGroupKeys, channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden, - smartActions, smartReplies, audiblyAlerted); + smartActions, smartReplies, audiblyAlerted, noisy); return update; } @@ -195,6 +197,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index < 2; } + private boolean getNoisy(int index) { + return index < 1; + } + private ArrayList getPeople(String key, int index) { ArrayList people = new ArrayList<>(); for (int i = 0; i < index; i++) {