From ec9e1f09c727b87516681cf580dceda9acf4c9f5 Mon Sep 17 00:00:00 2001 From: Gus Prevas Date: Tue, 18 Dec 2018 15:28:12 -0500 Subject: [PATCH 1/3] Extracts heads-up/pulsing logic from NotificationEntryManager. This change introduces the NotificationInterruptionStateProvider component, which contains all the logic around whether a notification should heads-up or pulse previously contained in NotificationEntryManager. We also introduce the NotificationFilter component which extracts logic about when to filter notifications from NotificationData, in order to break a circular dependency that would otherwise be introduced. As part of this, some additional fields from the notification ranking map are denormalized on to the NotificationData.Entry object. Test: atest SystemUITests, manually Change-Id: Ic61edca966a3c3e0b01f1a6a9e7ce79c8701da4e --- .../android/systemui/CarSystemUIFactory.java | 8 + .../car/CarNotificationEntryManager.java | 13 - ...NotificationInterruptionStateProvider.java | 42 +++ .../src/com/android/systemui/Dependency.java | 7 + .../com/android/systemui/SystemUIFactory.java | 8 + .../NotificationViewHierarchyManager.java | 1 - .../notification/NotificationData.java | 244 ++++--------- .../NotificationEntryManager.java | 286 +-------------- .../notification/NotificationFilter.java | 162 +++++++++ ...NotificationInterruptionStateProvider.java | 332 ++++++++++++++++++ .../notification/NotificationRowBinder.java | 12 +- .../phone/NotificationIconAreaController.java | 2 +- .../systemui/statusbar/phone/StatusBar.java | 6 +- .../phone/StatusBarNotificationPresenter.java | 8 +- .../notification/NotificationDataTest.java | 138 +------- .../NotificationEntryManagerTest.java | 1 - .../notification/NotificationFilterTest.java | 212 +++++++++++ .../statusbar/phone/StatusBarTest.java | 76 ++-- 18 files changed, 942 insertions(+), 616 deletions(-) create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java index 7039a2c0a9571..3c0a2973ad40f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java @@ -21,9 +21,11 @@ import android.content.Context; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.car.CarNotificationEntryManager; +import com.android.systemui.car.CarNotificationInterruptionStateProvider; import com.android.systemui.statusbar.car.CarFacetButtonController; import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.volume.CarVolumeDialogComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -67,6 +69,12 @@ public class CarSystemUIFactory extends SystemUIFactory { return new CarNotificationEntryManager(context); } + @Override + public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider( + Context context) { + return new CarNotificationInterruptionStateProvider(context); + } + @Module protected static class ContextHolder { private Context mContext; diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index 0563418e5fb90..323cae0067d8e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -18,7 +18,6 @@ package com.android.systemui.car; import android.content.Context; -import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -39,16 +38,4 @@ public class CarNotificationEntryManager extends NotificationEntryManager { // long click listener. return null; } - - @Override - public boolean shouldHeadsUp(NotificationData.Entry entry) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. Ensure this by not pinning any - // notification if the shade is already opened. - if (!getPresenter().isPresenterFullyCollapsed()) { - return false; - } - - return super.shouldHeadsUp(entry); - } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java new file mode 100644 index 0000000000000..62502ef60e96d --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car; + +import android.content.Context; + +import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; + +/** Auto-specific implementation of {@link NotificationInterruptionStateProvider}. */ +public class CarNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + public CarNotificationInterruptionStateProvider(Context context) { + super(context); + } + + @Override + public boolean shouldHeadsUp(NotificationData.Entry entry) { + // Because space is usually constrained in the auto use-case, there should not be a + // pinned notification when the shade has been expanded. Ensure this by not pinning any + // notification if the shade is already opened. + if (!getPresenter().isPresenterFullyCollapsed()) { + return false; + } + + return super.shouldHeadsUp(entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 445d156d289ca..6b4d07a44d34c 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -56,6 +56,8 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -256,6 +258,8 @@ public class Dependency extends SystemUI { @Inject Lazy mNotificationLogger; @Inject Lazy mNotificationViewHierarchyManager; @Inject Lazy mNotificationRowBinder; + @Inject Lazy mNotificationFilter; + @Inject Lazy mNotificationInterruptionStateProvider; @Inject Lazy mKeyguardDismissUtil; @Inject Lazy mSmartReplyController; @Inject Lazy mRemoteInputQuickSettingsDisabler; @@ -425,6 +429,9 @@ public class Dependency extends SystemUI { mProviders.put(NotificationViewHierarchyManager.class, mNotificationViewHierarchyManager::get); mProviders.put(NotificationRowBinder.class, mNotificationRowBinder::get); + mProviders.put(NotificationFilter.class, mNotificationFilter::get); + mProviders.put(NotificationInterruptionStateProvider.class, + mNotificationInterruptionStateProvider::get); mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); mProviders.put(SmartReplyController.class, mSmartReplyController::get); mProviders.put(RemoteInputQuickSettingsDisabler.class, diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 7689dfc8d6b75..384a14c70e9c3 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; @@ -198,6 +199,13 @@ public class SystemUIFactory { return new NotificationListener(context); } + @Singleton + @Provides + public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider( + Context context) { + return new NotificationInterruptionStateProvider(context); + } + @Module protected static class ContextHolder { private Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 25247470bd889..017cda741e5f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -133,7 +133,6 @@ public class NotificationViewHierarchyManager { mAlwaysExpandNonGroupedNotification = res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); mStatusBarStateListener = new StatusBarStateListener(mBubbleController); - mEntryManager.setStatusBarStateListener(mStatusBarStateListener); Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); } 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 ef7e0fe5791f4..433a994ac0cc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -27,20 +27,15 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; -import android.Manifest; import android.annotation.NonNull; -import android.app.AppGlobals; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; import android.content.Context; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcelable; -import android.os.RemoteException; import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; @@ -58,17 +53,13 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; -import com.android.systemui.ForegroundServiceController; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpManager; import java.io.PrintWriter; @@ -83,14 +74,13 @@ import java.util.Objects; */ public class NotificationData { + private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class); + /** * These dependencies are late init-ed */ private KeyguardEnvironment mEnvironment; - private ShadeController mShadeController; private NotificationMediaManager mMediaManager; - private ForegroundServiceController mFsc; - private NotificationLockscreenUserManager mUserManager; private HeadsUpManager mHeadsUpManager; @@ -120,6 +110,9 @@ public class NotificationData { @NonNull public List systemGeneratedSmartActions = Collections.emptyList(); public CharSequence[] smartReplies = new CharSequence[0]; + @VisibleForTesting + public int suppressedVisualEffects; + public boolean suspended; private Entry parent; // our parent (if we're in a group) private ArrayList children = new ArrayList(); @@ -183,6 +176,8 @@ public class NotificationData { smartReplies = ranking.getSmartReplies() == null ? new CharSequence[0] : ranking.getSmartReplies().toArray(new CharSequence[0]); + suppressedVisualEffects = ranking.getSuppressedVisualEffects(); + suspended = ranking.isSuspended(); } public void setInterruption() { @@ -625,6 +620,71 @@ public class NotificationData { if (row == null) return true; return row.canViewBeDismissed(); } + + boolean isExemptFromDndVisualSuppression() { + if (isNotificationBlockedByPolicy(notification.getNotification())) { + return false; + } + + if ((notification.getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return true; + } + if (notification.getNotification().isMediaNotification()) { + return true; + } + if (mIsSystemNotification != null && mIsSystemNotification) { + return true; + } + return false; + } + + private boolean shouldSuppressVisualEffect(int effect) { + if (isExemptFromDndVisualSuppression()) { + return false; + } + return (suppressedVisualEffects & effect) != 0; + } + + /** + * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT} + * is set for this entry. + */ + public boolean shouldSuppressFullScreenIntent() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT); + } + + /** + * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK} + * is set for this entry. + */ + public boolean shouldSuppressPeek() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK); + } + + /** + * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR} + * is set for this entry. + */ + public boolean shouldSuppressStatusBar() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR); + } + + /** + * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT} + * is set for this entry. + */ + public boolean shouldSuppressAmbient() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT); + } + + /** + * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST} + * is set for this entry. + */ + public boolean shouldSuppressNotificationList() { + return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST); + } } private final ArrayMap mEntries = new ArrayMap<>(); @@ -706,13 +766,6 @@ public class NotificationData { return mEnvironment; } - private ShadeController getShadeController() { - if (mShadeController == null) { - mShadeController = Dependency.get(ShadeController.class); - } - return mShadeController; - } - private NotificationMediaManager getMediaManager() { if (mMediaManager == null) { mMediaManager = Dependency.get(NotificationMediaManager.class); @@ -720,20 +773,6 @@ public class NotificationData { return mMediaManager; } - private ForegroundServiceController getFsc() { - if (mFsc == null) { - mFsc = Dependency.get(ForegroundServiceController.class); - } - return mFsc; - } - - private NotificationLockscreenUserManager getUserManager() { - if (mUserManager == null) { - mUserManager = Dependency.get(NotificationLockscreenUserManager.class); - } - return mUserManager; - } - /** * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment} * @@ -778,7 +817,7 @@ public class NotificationData { } public Entry remove(String key, RankingMap ranking) { - Entry removed = null; + Entry removed; synchronized (mEntries) { removed = mEntries.remove(key); } @@ -849,62 +888,12 @@ public class NotificationData { return Ranking.VISIBILITY_NO_OVERRIDE; } - public boolean shouldSuppressFullScreenIntent(Entry entry) { - return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT); - } - - public boolean shouldSuppressPeek(Entry entry) { - return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_PEEK); - } - - public boolean shouldSuppressStatusBar(Entry entry) { - return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_STATUS_BAR); - } - - public boolean shouldSuppressAmbient(Entry entry) { - return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_AMBIENT); - } - - public boolean shouldSuppressNotificationList(Entry entry) { - return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_NOTIFICATION_LIST); - } - - private boolean shouldSuppressVisualEffect(Entry entry, int effect) { - if (isExemptFromDndVisualSuppression(entry)) { - return false; - } - String key = entry.key; - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0; - } - return false; - } - - protected boolean isExemptFromDndVisualSuppression(Entry entry) { - if (isNotificationBlockedByPolicy(entry.notification.getNotification())) { - return false; - } - - if ((entry.notification.getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return true; - } - if (entry.notification.getNotification().isMediaNotification()) { - return true; - } - if (entry.mIsSystemNotification != null && entry.mIsSystemNotification) { - return true; - } - return false; - } - /** * Categories that are explicitly called out on DND settings screens are always blocked, if * DND has flagged them, even if they are foreground or system notifications that might * otherwise visually bypass DND. */ - protected boolean isNotificationBlockedByPolicy(Notification n) { + private static boolean isNotificationBlockedByPolicy(Notification n) { if (isCategory(CATEGORY_CALL, n) || isCategory(CATEGORY_MESSAGE, n) || isCategory(CATEGORY_ALARM, n) @@ -915,7 +904,7 @@ public class NotificationData { return false; } - private boolean isCategory(String category, Notification n) { + private static boolean isCategory(String category, Notification n) { return Objects.equals(n.category, category); } @@ -1013,7 +1002,7 @@ public class NotificationData { for (int i = 0; i < N; i++) { Entry entry = mEntries.valueAt(i); - if (shouldFilterOut(entry)) { + if (mNotificationFilter.shouldFilterOut(entry)) { continue; } @@ -1024,87 +1013,6 @@ public class NotificationData { Collections.sort(mSortedAndFiltered, mRankingComparator); } - /** - * @return true if this notification should NOT be shown right now - */ - public boolean shouldFilterOut(Entry entry) { - final StatusBarNotification sbn = entry.notification; - if (!(getEnvironment().isDeviceProvisioned() || - showNotificationEvenIfUnprovisioned(sbn))) { - return true; - } - - if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) { - return true; - } - - if (getUserManager().isLockscreenPublicMode(sbn.getUserId()) && - (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET - || getUserManager().shouldHideNotifications(sbn.getUserId()) - || getUserManager().shouldHideNotifications(sbn.getKey()))) { - return true; - } - - if (getShadeController().isDozing() && shouldSuppressAmbient(entry)) { - return true; - } - - if (!getShadeController().isDozing() && shouldSuppressNotificationList(entry)) { - return true; - } - - if (shouldHide(sbn.getKey())) { - return true; - } - - if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS - && mGroupManager.isChildInGroupWithSummary(sbn)) { - return true; - } - - if (getFsc().isDungeonNotification(sbn) - && !getFsc().isDungeonNeededForUser(sbn.getUserId())) { - // this is a foreground-service disclosure for a user that does not need to show one - return true; - } - if (getFsc().isSystemAlertNotification(sbn)) { - final String[] apps = sbn.getNotification().extras.getStringArray( - Notification.EXTRA_FOREGROUND_APPS); - if (apps != null && apps.length >= 1) { - if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) { - return true; - } - } - } - - return false; - } - - // Q: What kinds of notifications should show during setup? - // A: Almost none! Only things coming from packages with permission - // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them - // as relevant for setup (see below). - public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { - return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn); - } - - @VisibleForTesting - static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager, - StatusBarNotification sbn) { - return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP, - sbn.getUid()) == PackageManager.PERMISSION_GRANTED - && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); - } - - private static int checkUidPermission(IPackageManager packageManager, String permission, - int uid) { - try { - return packageManager.checkUidPermission(permission, uid); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - public void dump(PrintWriter pw, String indent) { int N = mSortedAndFiltered.size(); pw.print(indent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 535ea624dfc27..bcbe860ad7d41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification; import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT; import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; @@ -26,20 +25,16 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; -import android.database.ContentObserver; -import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; @@ -64,7 +59,6 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.NotificationUpdateHandler; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -97,8 +91,6 @@ public class NotificationEntryManager implements BubbleController.BubbleDismissListener { private static final String TAG = "NotificationEntryMgr"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean ENABLE_HEADS_UP = true; - private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); @@ -119,6 +111,8 @@ public class NotificationEntryManager implements private final AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); private final BubbleController mBubbleController = Dependency.get(BubbleController.class); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); // Lazily retrieved dependencies private NotificationRemoteInputManager mRemoteInputManager; @@ -138,14 +132,10 @@ public class NotificationEntryManager implements private NotificationListenerService.RankingMap mLatestRankingMap; protected HeadsUpManager mHeadsUpManager; protected NotificationData mNotificationData; - private ContentObserver mHeadsUpObserver; - protected boolean mUseHeadsUp = false; - private boolean mDisableNotificationAlerts; protected NotificationListContainer mListContainer; @VisibleForTesting final ArrayList mNotificationLifetimeExtenders = new ArrayList<>(); - private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener; @Nullable private AlertTransferListener mAlertTransferListener; private final DeviceProvisionedController.DeviceProvisionedListener @@ -157,11 +147,6 @@ public class NotificationEntryManager implements } }; - public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { - mDisableNotificationAlerts = disableNotificationAlerts; - mHeadsUpObserver.onChange(true); - } - public void destroy() { mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); } @@ -177,8 +162,6 @@ public class NotificationEntryManager implements pw.println(entry.notification); } } - pw.print(" mUseHeadsUp="); - pw.println(mUseHeadsUp); } public NotificationEntryManager(Context context) { @@ -245,36 +228,6 @@ public class NotificationEntryManager implements mNotificationData.setHeadsUpManager(mHeadsUpManager); mListContainer = listContainer; - mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) { - @Override - public void onChange(boolean selfChange) { - boolean wasUsing = mUseHeadsUp; - mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts - && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, - Settings.Global.HEADS_UP_OFF); - Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); - if (wasUsing != mUseHeadsUp) { - if (!mUseHeadsUp) { - Log.d(TAG, - "dismissing any existing heads up notification on disable event"); - mHeadsUpManager.releaseAllImmediately(); - } - } - } - }; - - if (ENABLE_HEADS_UP) { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), - true, - mHeadsUpObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, - mHeadsUpObserver); - } - mNotificationLifetimeExtenders.add(mHeadsUpManager); mNotificationLifetimeExtenders.add(mAmbientPulseManager); mNotificationLifetimeExtenders.add(mGutsManager); @@ -285,20 +238,6 @@ public class NotificationEntryManager implements } mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - - mHeadsUpObserver.onChange(true); // set up - - getRowBinder().setInterruptionStateProvider(new InterruptionStateProvider() { - @Override - public boolean shouldHeadsUp(NotificationData.Entry entry) { - return NotificationEntryManager.this.shouldHeadsUp(entry); - } - - @Override - public boolean shouldPulse(NotificationData.Entry entry) { - return NotificationEntryManager.this.shouldPulse(entry); - } - }); } public NotificationData getNotificationData() { @@ -327,7 +266,7 @@ public class NotificationEntryManager implements return true; } - return mNotificationData.shouldSuppressFullScreenIntent(entry); + return entry.shouldSuppressFullScreenIntent(); } public void performRemoveNotification(StatusBarNotification n) { @@ -441,7 +380,7 @@ public class NotificationEntryManager implements if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { // Possible for shouldHeadsUp to change between the inflation starting and ending. // If it does and we no longer need to heads up, we should free the view. - if (shouldHeadsUp(entry)) { + if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { mHeadsUpManager.showNotification(entry); // Mark as seen immediately setNotificationShown(entry.notification); @@ -450,7 +389,7 @@ public class NotificationEntryManager implements } } if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) { - if (shouldPulse(entry)) { + if (mNotificationInterruptionStateProvider.shouldPulse(entry)) { mAmbientPulseManager.showNotification(entry); } else { entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT); @@ -651,7 +590,7 @@ public class NotificationEntryManager implements NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); rankingMap.getRanking(key, ranking); NotificationData.Entry shadeEntry = createNotificationEntry(notification, ranking); - boolean isHeadsUped = shouldHeadsUp(shadeEntry); + boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(shadeEntry); if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { if (shouldSuppressFullScreenIntent(shadeEntry)) { if (DEBUG) { @@ -767,9 +706,11 @@ public class NotificationEntryManager implements boolean alertAgain = alertAgain(entry, entry.notification.getNotification()); if (getShadeController().isDozing()) { - updateAlertState(entry, shouldPulse(entry), alertAgain, mAmbientPulseManager); + updateAlertState(entry, mNotificationInterruptionStateProvider.shouldPulse(entry), + alertAgain, mAmbientPulseManager); } else { - updateAlertState(entry, shouldHeadsUp(entry), alertAgain, mHeadsUpManager); + updateAlertState(entry, mNotificationInterruptionStateProvider.shouldHeadsUp(entry), + alertAgain, mHeadsUpManager); } updateNotifications(); @@ -828,7 +769,7 @@ public class NotificationEntryManager implements // By comparing the old and new UI adjustments, reinflate the view accordingly. for (NotificationData.Entry entry : entries) { - mNotificationRowBinder.onNotificationRankingUpdated( + getRowBinder().onNotificationRankingUpdated( entry, oldImportances.get(entry.key), oldAdjustments.get(entry.key), @@ -851,182 +792,6 @@ public class NotificationEntryManager implements } } - public void setStatusBarStateListener( - NotificationViewHierarchyManager.StatusBarStateListener listener) { - mStatusBarStateListener = listener; - } - - /** - * Whether the notification should peek in from the top and alert the user. - * - * @param entry the entry to check - * @return true if the entry should heads up, false otherwise - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) - public boolean shouldHeadsUp(NotificationData.Entry entry) { - StatusBarNotification sbn = entry.notification; - - if (getShadeController().isDozing()) { - if (DEBUG) { - Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey()); - } - return false; - } - - // TODO: need to changes this, e.g. should still heads up in expanded shade, might want - // message bubble from the bubble to go through heads up path - boolean inShade = mStatusBarStateListener != null - && mStatusBarStateListener.getCurrentState() == SHADE; - if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) { - return false; - } - - if (!canAlertCommon(entry)) { - if (DEBUG) { - Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey()); - } - return false; - } - - if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { - if (DEBUG) { - Log.d(TAG, "No heads up: no huns or vr mode"); - } - return false; - } - - boolean isDreaming = false; - try { - isDreaming = mDreamManager.isDreaming(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to query dream manager.", e); - } - boolean inUse = mPowerManager.isScreenOn() && !isDreaming; - - if (!inUse) { - if (DEBUG) { - Log.d(TAG, "No heads up: not in use: " + sbn.getKey()); - } - return false; - } - - if (mNotificationData.shouldSuppressPeek(entry)) { - if (DEBUG) { - Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); - } - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) { - Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); - } - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) { - Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); - } - return false; - } - - if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); - } - return false; - } - - if (!mCallback.canHeadsUp(entry, sbn)) { - return false; - } - - return true; - } - - /** - * Whether or not the notification should "pulse" on the user's display when the phone is - * dozing. This displays the ambient view of the notification. - * - * @param entry the entry to check - * @return true if the entry should ambient pulse, false otherwise - */ - private boolean shouldPulse(NotificationData.Entry entry) { - StatusBarNotification sbn = entry.notification; - - if (!getShadeController().isDozing()) { - if (DEBUG) { - Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey()); - } - return false; - } - - if (!canAlertCommon(entry)) { - if (DEBUG) { - Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey()); - } - return false; - } - - if (mNotificationData.shouldSuppressAmbient(entry)) { - if (DEBUG) { - Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey()); - } - return false; - } - - if (mNotificationData.getImportance(sbn.getKey()) - < NotificationManager.IMPORTANCE_DEFAULT) { - if (DEBUG) { - Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey()); - } - return false; - } - - Bundle extras = sbn.getNotification().extras; - CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); - CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT); - if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) { - if (DEBUG) { - Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey()); - } - return false; - } - - return true; - } - - /** - * Common checks between heads up alerting and ambient pulse alerting. See - * {@link NotificationEntryManager#shouldHeadsUp(NotificationData.Entry)} and - * {@link NotificationEntryManager#shouldPulse(NotificationData.Entry)}. Notifications that - * fail any of these checks should not alert at all. - * - * @param entry the entry to check - * @return true if these checks pass, false if the notification should not alert - */ - protected boolean canAlertCommon(NotificationData.Entry entry) { - StatusBarNotification sbn = entry.notification; - - if (mNotificationData.shouldFilterOut(entry)) { - if (DEBUG) { - Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey()); - } - return false; - } - - // Don't alert notifications that are suppressed due to group alert behavior - if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - if (DEBUG) { - Log.d(TAG, "No alerting: suppressed due to group alert behavior"); - } - return false; - } - - return true; - } - private void setNotificationShown(StatusBarNotification n) { setNotificationsShown(new String[]{n.getKey()}); } @@ -1039,10 +804,6 @@ public class NotificationEntryManager implements } } - private boolean isSnoozedPackage(StatusBarNotification sbn) { - return mHeadsUpManager.isSnoozed(sbn.getPackageName()); - } - /** * Update the entry's alert state and call the appropriate {@link AlertingNotificationManager} * method. @@ -1077,22 +838,6 @@ public class NotificationEntryManager implements return mPendingNotifications.values(); } - /** - * Interface for retrieving heads-up and pulsing state for an entry. - */ - public interface InterruptionStateProvider { - /** - * Whether the provided entry should be marked as heads-up when inflated. - */ - boolean shouldHeadsUp(NotificationData.Entry entry); - - /** - * Whether the provided entry should be marked as pulsing (displayed in ambient) when - * inflated. - */ - boolean shouldPulse(NotificationData.Entry entry); - } - /** * Callback for NotificationEntryManager. */ @@ -1126,14 +871,5 @@ public class NotificationEntryManager implements * @param statusBarNotification notification that is being removed */ void onPerformRemoveNotification(StatusBarNotification statusBarNotification); - - /** - * Returns true if NotificationEntryManager can heads up this notification. - * - * @param entry entry of the notification that might be heads upped - * @param sbn notification that might be heads upped - * @return true if the notification can be heads upped - */ - boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java new file mode 100644 index 0000000000000..5e99c388655f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.Manifest; +import android.app.AppGlobals; +import android.app.Notification; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBar; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Component which manages the various reasons a notification might be filtered out. */ +@Singleton +public class NotificationFilter { + + private final NotificationGroupManager mGroupManager = Dependency.get( + NotificationGroupManager.class); + + private NotificationData.KeyguardEnvironment mEnvironment; + private ShadeController mShadeController; + private ForegroundServiceController mFsc; + private NotificationLockscreenUserManager mUserManager; + + @Inject + public NotificationFilter() {} + + private NotificationData.KeyguardEnvironment getEnvironment() { + if (mEnvironment == null) { + mEnvironment = Dependency.get(NotificationData.KeyguardEnvironment.class); + } + return mEnvironment; + } + + private ShadeController getShadeController() { + if (mShadeController == null) { + mShadeController = Dependency.get(ShadeController.class); + } + return mShadeController; + } + + private ForegroundServiceController getFsc() { + if (mFsc == null) { + mFsc = Dependency.get(ForegroundServiceController.class); + } + return mFsc; + } + + private NotificationLockscreenUserManager getUserManager() { + if (mUserManager == null) { + mUserManager = Dependency.get(NotificationLockscreenUserManager.class); + } + return mUserManager; + } + + + /** + * @return true if the provided notification should NOT be shown right now. + */ + public boolean shouldFilterOut(NotificationData.Entry entry) { + final StatusBarNotification sbn = entry.notification; + if (!(getEnvironment().isDeviceProvisioned() + || showNotificationEvenIfUnprovisioned(sbn))) { + return true; + } + + if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) { + return true; + } + + if (getUserManager().isLockscreenPublicMode(sbn.getUserId()) + && (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET + || getUserManager().shouldHideNotifications(sbn.getUserId()) + || getUserManager().shouldHideNotifications(sbn.getKey()))) { + return true; + } + + if (getShadeController().isDozing() && entry.shouldSuppressAmbient()) { + return true; + } + + if (!getShadeController().isDozing() && entry.shouldSuppressNotificationList()) { + return true; + } + + if (entry.suspended) { + return true; + } + + if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS + && mGroupManager.isChildInGroupWithSummary(sbn)) { + return true; + } + + if (getFsc().isDungeonNotification(sbn) + && !getFsc().isDungeonNeededForUser(sbn.getUserId())) { + // this is a foreground-service disclosure for a user that does not need to show one + return true; + } + if (getFsc().isSystemAlertNotification(sbn)) { + final String[] apps = sbn.getNotification().extras.getStringArray( + Notification.EXTRA_FOREGROUND_APPS); + if (apps != null && apps.length >= 1) { + if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) { + return true; + } + } + } + + return false; + } + + // Q: What kinds of notifications should show during setup? + // A: Almost none! Only things coming from packages with permission + // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them + // as relevant for setup (see below). + private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { + return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn); + } + + @VisibleForTesting + static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager, + StatusBarNotification sbn) { + return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP, + sbn.getUid()) == PackageManager.PERMISSION_GRANTED + && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); + } + + private static int checkUidPermission(IPackageManager packageManager, String permission, + int uid) { + try { + return packageManager.checkUidPermission(permission, uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java new file mode 100644 index 0000000000000..8bd0e9ad3c8b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import static com.android.systemui.statusbar.StatusBarState.SHADE; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.policy.HeadsUpManager; + +/** + * Provides heads-up and pulsing state for notification entries. + */ +public class NotificationInterruptionStateProvider { + + private static final String TAG = "InterruptionStateProvider"; + private static final boolean DEBUG = false; + private static final boolean ENABLE_HEADS_UP = true; + private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; + + private final StatusBarStateController mStatusBarStateController = + Dependency.get(StatusBarStateController.class); + private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class); + + private final Context mContext; + private final PowerManager mPowerManager; + private final IDreamManager mDreamManager; + + private NotificationPresenter mPresenter; + private ShadeController mShadeController; + private HeadsUpManager mHeadsUpManager; + private HeadsUpSuppressor mHeadsUpSuppressor; + + private ContentObserver mHeadsUpObserver; + @VisibleForTesting + protected boolean mUseHeadsUp = false; + private boolean mDisableNotificationAlerts; + + public NotificationInterruptionStateProvider(Context context) { + this(context, + (PowerManager) context.getSystemService(Context.POWER_SERVICE), + IDreamManager.Stub.asInterface( + ServiceManager.checkService(DreamService.DREAM_SERVICE))); + } + + @VisibleForTesting + protected NotificationInterruptionStateProvider( + Context context, + PowerManager powerManager, + IDreamManager dreamManager) { + mContext = context; + mPowerManager = powerManager; + mDreamManager = dreamManager; + } + + /** Sets up late-binding dependencies for this component. */ + public void setUpWithPresenter( + NotificationPresenter notificationPresenter, + HeadsUpManager headsUpManager, + HeadsUpSuppressor headsUpSuppressor) { + mPresenter = notificationPresenter; + mHeadsUpManager = headsUpManager; + mHeadsUpSuppressor = headsUpSuppressor; + + mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) { + @Override + public void onChange(boolean selfChange) { + boolean wasUsing = mUseHeadsUp; + mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts + && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF); + Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); + if (wasUsing != mUseHeadsUp) { + if (!mUseHeadsUp) { + Log.d(TAG, + "dismissing any existing heads up notification on disable event"); + mHeadsUpManager.releaseAllImmediately(); + } + } + } + }; + + if (ENABLE_HEADS_UP) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), + true, + mHeadsUpObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, + mHeadsUpObserver); + } + mHeadsUpObserver.onChange(true); // set up + } + + private ShadeController getShadeController() { + if (mShadeController == null) { + mShadeController = Dependency.get(ShadeController.class); + } + return mShadeController; + } + + /** + * Whether the notification should peek in from the top and alert the user. + * + * @param entry the entry to check + * @return true if the entry should heads up, false otherwise + */ + public boolean shouldHeadsUp(NotificationData.Entry entry) { + StatusBarNotification sbn = entry.notification; + + if (getShadeController().isDozing()) { + if (DEBUG) { + Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey()); + } + return false; + } + + // TODO: need to changes this, e.g. should still heads up in expanded shade, might want + // message bubble from the bubble to go through heads up path + boolean inShade = mStatusBarStateController.getState() == SHADE; + if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) { + return false; + } + + if (!canAlertCommon(entry)) { + if (DEBUG) { + Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey()); + } + return false; + } + + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) { + Log.d(TAG, "No heads up: no huns or vr mode"); + } + return false; + } + + boolean isDreaming = false; + try { + isDreaming = mDreamManager.isDreaming(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query dream manager.", e); + } + boolean inUse = mPowerManager.isScreenOn() && !isDreaming; + + if (!inUse) { + if (DEBUG) { + Log.d(TAG, "No heads up: not in use: " + sbn.getKey()); + } + return false; + } + + if (entry.shouldSuppressPeek()) { + if (DEBUG) { + Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey()); + } + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) { + Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey()); + } + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) { + Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey()); + } + return false; + } + + if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey()); + } + return false; + } + + if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) { + return false; + } + + return true; + } + + /** + * Whether or not the notification should "pulse" on the user's display when the phone is + * dozing. This displays the ambient view of the notification. + * + * @param entry the entry to check + * @return true if the entry should ambient pulse, false otherwise + */ + public boolean shouldPulse(NotificationData.Entry entry) { + StatusBarNotification sbn = entry.notification; + + if (!getShadeController().isDozing()) { + if (DEBUG) { + Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey()); + } + return false; + } + + if (!canAlertCommon(entry)) { + if (DEBUG) { + Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey()); + } + return false; + } + + if (entry.shouldSuppressAmbient()) { + if (DEBUG) { + Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey()); + } + return false; + } + + if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) { + if (DEBUG) { + Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey()); + } + return false; + } + + Bundle extras = sbn.getNotification().extras; + CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); + CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT); + if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) { + if (DEBUG) { + Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey()); + } + return false; + } + + return true; + } + + /** + * Common checks between heads up alerting and ambient pulse alerting. See + * {@link #shouldHeadsUp(NotificationData.Entry)} and + * {@link #shouldPulse(NotificationData.Entry)}. Notifications that fail any of these checks + * should not alert at all. + * + * @param entry the entry to check + * @return true if these checks pass, false if the notification should not alert + */ + protected boolean canAlertCommon(NotificationData.Entry entry) { + StatusBarNotification sbn = entry.notification; + + if (mNotificationFilter.shouldFilterOut(entry)) { + if (DEBUG) { + Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey()); + } + return false; + } + + // Don't alert notifications that are suppressed due to group alert behavior + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + if (DEBUG) { + Log.d(TAG, "No alerting: suppressed due to group alert behavior"); + } + return false; + } + + return true; + } + + private boolean isSnoozedPackage(StatusBarNotification sbn) { + return mHeadsUpManager.isSnoozed(sbn.getPackageName()); + } + + /** Sets whether to disable all alerts. */ + public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { + mDisableNotificationAlerts = disableNotificationAlerts; + mHeadsUpObserver.onChange(true); + } + + protected NotificationPresenter getPresenter() { + return mPresenter; + } + + /** A component which can suppress heads-up notifications due to the overall state of the UI. */ + public interface HeadsUpSuppressor { + /** + * Returns false if the provided notification is ineligible for heads-up according to this + * component. + * + * @param entry entry of the notification that might be heads upped + * @param sbn notification that might be heads upped + * @return false if the notification can not be heads upped + */ + boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn); + + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java index 824bd813d5634..b241b8a70b3fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java @@ -64,6 +64,8 @@ public class NotificationRowBinder { private final NotificationGutsManager mGutsManager = Dependency.get(NotificationGutsManager.class); private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); private final Context mContext; private final IStatusBarService mBarService; @@ -79,7 +81,6 @@ public class NotificationRowBinder { private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; - private NotificationEntryManager.InterruptionStateProvider mInterruptionStateProvider; @Inject public NotificationRowBinder(Context context) { @@ -116,11 +117,6 @@ public class NotificationRowBinder { mNotificationClicker = clicker; } - public void setInterruptionStateProvider( - NotificationEntryManager.InterruptionStateProvider interruptionStateProvider) { - mInterruptionStateProvider = interruptionStateProvider; - } - /** * Inflates the views for the given entry (possibly asynchronously). */ @@ -253,10 +249,10 @@ public class NotificationRowBinder { row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); row.setEntry(entry); - if (mInterruptionStateProvider.shouldHeadsUp(entry)) { + if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */); } - if (mInterruptionStateProvider.shouldPulse(entry)) { + if (mNotificationInterruptionStateProvider.shouldPulse(entry)) { row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */); } row.setNeedsRedaction( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 2d5d562746404..e40835fb8fd07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -207,7 +207,7 @@ public class NotificationIconAreaController implements DarkReceiver { } // showAmbient == show in shade but not shelf - if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) { + if (!showAmbient && entry.shouldSuppressStatusBar()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1b43f8fad9bdc..008c21d1b936f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -191,6 +191,7 @@ import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -377,6 +378,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationGutsManager mGutsManager; protected NotificationLogger mNotificationLogger; protected NotificationEntryManager mEntryManager; + private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private NotificationRowBinder mNotificationRowBinder; protected NotificationViewHierarchyManager mViewHierarchyManager; protected ForegroundServiceController mForegroundServiceController; @@ -622,6 +624,8 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager = Dependency.get(NotificationGutsManager.class); mMediaManager = Dependency.get(NotificationMediaManager.class); mEntryManager = Dependency.get(NotificationEntryManager.class); + mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); mNotificationRowBinder = Dependency.get(NotificationRowBinder.class); mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); mForegroundServiceController = Dependency.get(ForegroundServiceController.class); @@ -1413,7 +1417,7 @@ public class StatusBar extends SystemUI implements DemoMode, } if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - mEntryManager.setDisableNotificationAlerts( + mNotificationInterruptionStateProvider.setDisableNotificationAlerts( (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index c8c9ebe596771..e69dea58f2fed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -91,6 +92,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, Dependency.get(NotificationEntryManager.class); private final NotificationRowBinder mNotificationRowBinder = Dependency.get(NotificationRowBinder.class); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); private final NotificationMediaManager mMediaManager = Dependency.get(NotificationMediaManager.class); protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); @@ -173,6 +176,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager); mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, mEntryManager, this); + mNotificationInterruptionStateProvider.setUpWithPresenter( + this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); Dependency.get(NotificationGutsManager.class).setUpWithPresenter(this, @@ -225,7 +230,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, @Override public void onPerformRemoveNotification(StatusBarNotification n) { if (mNotificationPanel.hasPulsingNotifications() && - !mAmbientPulseManager.hasNotifications()) { + !mAmbientPulseManager.hasNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); @@ -282,7 +287,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } - @Override public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) { if (mShadeController.isDozing()) { return false; 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 def7513bc7dd5..871ff8998c204 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 @@ -29,8 +29,6 @@ import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,7 +48,6 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; -import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -105,6 +102,7 @@ public class NotificationDataTest extends SysuiTestCase { com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper(); MockitoAnnotations.initMocks(this); when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL); + when(mMockStatusBarNotification.cloneLight()).thenReturn(mMockStatusBarNotification); when(mMockPackageManager.checkUidPermission( eq(Manifest.permission.NOTIFICATION_DURING_SETUP), @@ -128,41 +126,6 @@ public class NotificationDataTest extends SysuiTestCase { Dependency.get(InitController.class).executePostInitTasks(); } - @Test - @UiThreadTest - public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() { - initStatusBarNotification(false); - when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP); - - assertFalse( - NotificationData.showNotificationEvenIfUnprovisioned( - mMockPackageManager, - mMockStatusBarNotification)); - } - - @Test - @UiThreadTest - public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() { - initStatusBarNotification(true); - - assertFalse( - NotificationData.showNotificationEvenIfUnprovisioned( - mMockPackageManager, - mMockStatusBarNotification)); - } - - @Test - @UiThreadTest - public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() { - initStatusBarNotification(true); - when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP); - - assertTrue( - NotificationData.showNotificationEvenIfUnprovisioned( - mMockPackageManager, - mMockStatusBarNotification)); - } - @Test public void testChannelSetWhenAdded() { mNotificationData.add(mRow.getEntry()); @@ -229,76 +192,6 @@ public class NotificationDataTest extends SysuiTestCase { .mActiveAppOps.contains(OP_ACCEPT_HANDOVER)); } - @Test - public void testSuppressSystemAlertNotification() { - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); - when(mFsc.isSystemAlertNotification(any())).thenReturn(true); - StatusBarNotification sbn = mRow.getEntry().notification; - Bundle bundle = new Bundle(); - bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"}); - sbn.getNotification().extras = bundle; - - assertTrue(mNotificationData.shouldFilterOut(mRow.getEntry())); - } - - @Test - public void testDoNotSuppressSystemAlertNotification() { - StatusBarNotification sbn = mRow.getEntry().notification; - Bundle bundle = new Bundle(); - bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"}); - sbn.getNotification().extras = bundle; - - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); - when(mFsc.isSystemAlertNotification(any())).thenReturn(true); - - assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry())); - - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); - when(mFsc.isSystemAlertNotification(any())).thenReturn(false); - - assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry())); - - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); - when(mFsc.isSystemAlertNotification(any())).thenReturn(false); - - assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry())); - } - - @Test - public void testDoNotSuppressMalformedSystemAlertNotification() { - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); - - // missing extra - assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry())); - - StatusBarNotification sbn = mRow.getEntry().notification; - Bundle bundle = new Bundle(); - bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {}); - sbn.getNotification().extras = bundle; - - // extra missing values - assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry())); - } - - @Test - public void testShouldFilterHiddenNotifications() { - initStatusBarNotification(false); - // setup - when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); - when(mFsc.isSystemAlertNotification(any())).thenReturn(false); - - // test should filter out hidden notifications: - // hidden - when(mMockStatusBarNotification.getKey()).thenReturn(TEST_HIDDEN_NOTIFICATION_KEY); - NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); - assertTrue(mNotificationData.shouldFilterOut(entry)); - - // not hidden - when(mMockStatusBarNotification.getKey()).thenReturn("not hidden"); - entry = new NotificationData.Entry(mMockStatusBarNotification); - assertFalse(mNotificationData.shouldFilterOut(entry)); - } - @Test public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() throws Exception { @@ -325,9 +218,10 @@ public class NotificationDataTest extends SysuiTestCase { Notification n = mMockStatusBarNotification.getNotification(); n.flags = Notification.FLAG_FOREGROUND_SERVICE; NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); + mNotificationData.add(entry); - assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry)); - assertFalse(mNotificationData.shouldSuppressAmbient(entry)); + assertTrue(entry.isExemptFromDndVisualSuppression()); + assertFalse(entry.shouldSuppressAmbient()); } @Test @@ -341,9 +235,10 @@ public class NotificationDataTest extends SysuiTestCase { n = nb.build(); when(mMockStatusBarNotification.getNotification()).thenReturn(n); NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); + mNotificationData.add(entry); - assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry)); - assertFalse(mNotificationData.shouldSuppressAmbient(entry)); + assertTrue(entry.isExemptFromDndVisualSuppression()); + assertFalse(entry.shouldSuppressAmbient()); } @Test @@ -353,9 +248,10 @@ public class NotificationDataTest extends SysuiTestCase { TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY); NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); entry.mIsSystemNotification = true; + mNotificationData.add(entry); - assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry)); - assertFalse(mNotificationData.shouldSuppressAmbient(entry)); + assertTrue(entry.isExemptFromDndVisualSuppression()); + assertFalse(entry.shouldSuppressAmbient()); } @Test @@ -365,31 +261,33 @@ public class NotificationDataTest extends SysuiTestCase { TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY); NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); entry.mIsSystemNotification = true; + mNotificationData.add(entry); + when(mMockStatusBarNotification.getNotification()).thenReturn( new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build()); - assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry)); - assertTrue(mNotificationData.shouldSuppressAmbient(entry)); + assertFalse(entry.isExemptFromDndVisualSuppression()); + assertTrue(entry.shouldSuppressAmbient()); when(mMockStatusBarNotification.getNotification()).thenReturn( new Notification.Builder(mContext, "").setCategory(CATEGORY_REMINDER).build()); - assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry)); + assertFalse(entry.isExemptFromDndVisualSuppression()); when(mMockStatusBarNotification.getNotification()).thenReturn( new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build()); - assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry)); + assertFalse(entry.isExemptFromDndVisualSuppression()); when(mMockStatusBarNotification.getNotification()).thenReturn( new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build()); - assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry)); + assertFalse(entry.isExemptFromDndVisualSuppression()); when(mMockStatusBarNotification.getNotification()).thenReturn( new Notification.Builder(mContext, "").setCategory(CATEGORY_MESSAGE).build()); - assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry)); + assertFalse(entry.isExemptFromDndVisualSuppression()); } @Test 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 8fe91cd5e82a0..768ba8a32f58d 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 @@ -137,7 +137,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { super(context); mBarService = barService; mCountDownLatch = new CountDownLatch(1); - mUseHeadsUp = true; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java new file mode 100644 index 0000000000000..da8bc01dc29da --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.Notification; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.service.notification.StatusBarNotification; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; + +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.ShadeController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class NotificationFilterTest extends SysuiTestCase { + + private static final int UID_NORMAL = 123; + private static final int UID_ALLOW_DURING_SETUP = 456; + private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey"; + + private final StatusBarNotification mMockStatusBarNotification = + mock(StatusBarNotification.class); + + @Mock + ForegroundServiceController mFsc; + @Mock + NotificationData.KeyguardEnvironment mEnvironment; + private final IPackageManager mMockPackageManager = mock(IPackageManager.class); + + private NotificationFilter mNotificationFilter; + private ExpandableNotificationRow mRow; + + @Before + public void setUp() throws Exception { + com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper(); + MockitoAnnotations.initMocks(this); + when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL); + + when(mMockPackageManager.checkUidPermission( + eq(Manifest.permission.NOTIFICATION_DURING_SETUP), + eq(UID_NORMAL))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mMockPackageManager.checkUidPermission( + eq(Manifest.permission.NOTIFICATION_DURING_SETUP), + eq(UID_ALLOW_DURING_SETUP))) + .thenReturn(PackageManager.PERMISSION_GRANTED); + mDependency.injectTestDependency(ForegroundServiceController.class, mFsc); + mDependency.injectTestDependency(NotificationGroupManager.class, + new NotificationGroupManager()); + mDependency.injectMockDependency(ShadeController.class); + mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment); + when(mEnvironment.isDeviceProvisioned()).thenReturn(true); + when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); + mRow = new NotificationTestHelper(getContext()).createRow(); + mNotificationFilter = new NotificationFilter(); + } + + @Test + @UiThreadTest + public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() { + initStatusBarNotification(false); + when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP); + + assertFalse( + NotificationFilter.showNotificationEvenIfUnprovisioned( + mMockPackageManager, + mMockStatusBarNotification)); + } + + @Test + @UiThreadTest + public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() { + initStatusBarNotification(true); + + assertFalse( + NotificationFilter.showNotificationEvenIfUnprovisioned( + mMockPackageManager, + mMockStatusBarNotification)); + } + + @Test + @UiThreadTest + public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() { + initStatusBarNotification(true); + when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP); + + assertTrue( + NotificationFilter.showNotificationEvenIfUnprovisioned( + mMockPackageManager, + mMockStatusBarNotification)); + } + + @Test + public void testSuppressSystemAlertNotification() { + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); + when(mFsc.isSystemAlertNotification(any())).thenReturn(true); + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"}); + sbn.getNotification().extras = bundle; + + assertTrue(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + } + + @Test + public void testDoNotSuppressSystemAlertNotification() { + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"}); + sbn.getNotification().extras = bundle; + + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + when(mFsc.isSystemAlertNotification(any())).thenReturn(true); + + assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + when(mFsc.isSystemAlertNotification(any())).thenReturn(false); + + assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); + when(mFsc.isSystemAlertNotification(any())).thenReturn(false); + + assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + } + + @Test + public void testDoNotSuppressMalformedSystemAlertNotification() { + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true); + + // missing extra + assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + + StatusBarNotification sbn = mRow.getEntry().notification; + Bundle bundle = new Bundle(); + bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{}); + sbn.getNotification().extras = bundle; + + // extra missing values + assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry())); + } + + @Test + public void testShouldFilterHiddenNotifications() { + initStatusBarNotification(false); + // setup + when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false); + when(mFsc.isSystemAlertNotification(any())).thenReturn(false); + + // test should filter out hidden notifications: + // hidden + NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification); + entry.suspended = true; + assertTrue(mNotificationFilter.shouldFilterOut(entry)); + + // not hidden + entry = new NotificationData.Entry(mMockStatusBarNotification); + entry.suspended = false; + assertFalse(mNotificationFilter.shouldFilterOut(entry)); + } + + private void initStatusBarNotification(boolean allowDuringSetup) { + Bundle bundle = new Bundle(); + bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); + Notification notification = new Notification.Builder(mContext, "test") + .addExtras(bundle) + .build(); + when(mMockStatusBarNotification.getNotification()).thenReturn(notification); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index e5620a5520d5e..39c420388f783 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -92,6 +93,8 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -129,6 +132,8 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ArrayList mNotificationList; @Mock private BiometricUnlockController mBiometricUnlockController; @Mock private NotificationData mNotificationData; + @Mock + private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; // Mock dependencies: @Mock private NotificationViewHierarchyManager mViewHierarchyManager; @@ -143,11 +148,14 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationPresenter mNotificationPresenter; @Mock private NotificationEntryManager.Callback mCallback; @Mock private BubbleController mBubbleController; + @Mock + private NotificationFilter mNotificationFilter; private TestableStatusBar mStatusBar; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; private TestableNotificationEntryManager mEntryManager; + private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private NotificationLogger mNotificationLogger; private CommandQueue mCommandQueue; @@ -168,6 +176,17 @@ public class StatusBarTest extends SysuiTestCase { mDependency.injectTestDependency(DeviceProvisionedController.class, mDeviceProvisionedController); mDependency.injectMockDependency(BubbleController.class); + mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter); + + IPowerManager powerManagerService = mock(IPowerManager.class); + mPowerManager = new PowerManager(mContext, powerManagerService, + Handler.createAsync(Looper.myLooper())); + + mNotificationInterruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, mPowerManager, + mDreamManager); + mDependency.injectTestDependency(NotificationInterruptionStateProvider.class, + mNotificationInterruptionStateProvider); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -178,10 +197,6 @@ public class StatusBarTest extends SysuiTestCase { mNotificationLogger = new NotificationLogger(); DozeLog.traceDozing(mContext, false /* dozing */); - IPowerManager powerManagerService = mock(IPowerManager.class); - mPowerManager = new PowerManager(mContext, powerManagerService, - Handler.createAsync(Looper.myLooper())); - mCommandQueue = mock(CommandQueue.class); when(mCommandQueue.asBinder()).thenReturn(new Binder()); mContext.putComponent(CommandQueue.class, mCommandQueue); @@ -205,6 +220,9 @@ public class StatusBarTest extends SysuiTestCase { return null; }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); + mNotificationInterruptionStateProvider.setUpWithPresenter(mNotificationPresenter, + mHeadsUpManager, mHeadsUpSuppressor); + mEntryManager = new TestableNotificationEntryManager(mDreamManager, mPowerManager, mContext); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, @@ -362,11 +380,9 @@ public class StatusBarTest extends SysuiTestCase { public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false); - when(mNotificationData.shouldFilterOut(any())).thenReturn(false); + when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); - when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH); - when(mCallback.canHeadsUp(any(), any())).thenReturn(true); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -376,19 +392,18 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); + entry.importance = IMPORTANCE_HIGH; - assertTrue(mEntryManager.shouldHeadsUp(entry)); + assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test public void testShouldHeadsUp_suppressedGroupSummary() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false); - when(mNotificationData.shouldFilterOut(any())).thenReturn(false); + when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); - when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH); - when(mCallback.canHeadsUp(any(), any())).thenReturn(true); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a") .setGroup("a") @@ -398,46 +413,44 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); + entry.importance = IMPORTANCE_HIGH; - assertFalse(mEntryManager.shouldHeadsUp(entry)); + assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test public void testShouldHeadsUp_suppressedHeadsUp() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mNotificationData.shouldFilterOut(any())).thenReturn(false); + when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); - when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH); - when(mCallback.canHeadsUp(any(), any())).thenReturn(true); - - when(mNotificationData.shouldSuppressPeek(any())).thenReturn(true); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a").build(); StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); + entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK; + entry.importance = IMPORTANCE_HIGH; - assertFalse(mEntryManager.shouldHeadsUp(entry)); + assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception { when(mPowerManager.isScreenOn()).thenReturn(true); when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mNotificationData.shouldFilterOut(any())).thenReturn(false); + when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); - when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH); - when(mCallback.canHeadsUp(any(), any())).thenReturn(true); - - when(mNotificationData.shouldSuppressPeek(any())).thenReturn(false); + when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true); Notification n = new Notification.Builder(getContext(), "a").build(); StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); + entry.importance = IMPORTANCE_HIGH; - assertTrue(mEntryManager.shouldHeadsUp(entry)); + assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry)); } @Test @@ -739,6 +752,17 @@ public class StatusBarTest extends SysuiTestCase { NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); mNotificationData = notificationData; + } + } + + public static class TestableNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + + public TestableNotificationInterruptionStateProvider( + Context context, + PowerManager powerManager, + IDreamManager dreamManager) { + super(context, powerManager, dreamManager); mUseHeadsUp = true; } } From d65c2db9234a74f77386ef4c1b60887f2046a755 Mon Sep 17 00:00:00 2001 From: Gus Prevas Date: Tue, 18 Dec 2018 17:13:38 -0500 Subject: [PATCH 2/3] Moves fullscreen intent logic to NotificationActivityStarter. This change generalizes the AlertTransferListener interface as described in its javadoc in order to make it a general-purpose listener interface for changes managed by NotificationEntryManager, then uses this mechanism to move handling of fullscreen intents on incoming notifications from NotificationEntryManager to StatusBarNotificationActivityStarter. Test: atest NotificationEntryManagerTest Change-Id: Ie8cb510466b2750fa74ef093dcad0726cf17210d --- ...er.java => NotificationEntryListener.java} | 15 ++-- .../NotificationEntryManager.java | 81 ++++--------------- .../NotificationGroupAlertTransferHelper.java | 7 +- .../StatusBarNotificationActivityStarter.java | 70 ++++++++++++++++ .../NotificationStackScrollLayoutTest.java | 2 +- ...ificationGroupAlertTransferHelperTest.java | 23 +++--- .../statusbar/phone/StatusBarTest.java | 6 +- 7 files changed, 111 insertions(+), 93 deletions(-) rename packages/SystemUI/src/com/android/systemui/statusbar/notification/{AlertTransferListener.java => NotificationEntryListener.java} (73%) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java similarity index 73% rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 13e991ba24312..4721994fe91da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -16,26 +16,27 @@ package com.android.systemui.statusbar.notification; /** - * Listener interface for when NotificationEntryManager needs to tell - * NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose - * listening interface for the NotificationEntryManager. + * Listener interface for changes sent by NotificationEntryManager. */ -public interface AlertTransferListener { +public interface NotificationEntryListener { /** * Called when a new notification is posted. At this point, the notification is "pending": its * views haven't been inflated yet and most of the system pretends like it doesn't exist yet. */ - void onPendingEntryAdded(NotificationData.Entry entry); + default void onPendingEntryAdded(NotificationData.Entry entry) { + } /** * Called when an existing notification's views are reinflated (usually due to an update being * posted to that notification). */ - void onEntryReinflated(NotificationData.Entry entry); + default void onEntryReinflated(NotificationData.Entry entry) { + } /** * Called when a notification has been removed (either because the user swiped it away or * because the developer retracted it). */ - void onEntryRemoved(NotificationData.Entry entry); + default void onEntryRemoved(NotificationData.Entry entry) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index bcbe860ad7d41..fdc5363168728 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -22,33 +22,25 @@ import static com.android.systemui.statusbar.notification.row.NotificationInflat import android.annotation.Nullable; import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.Context; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.service.dreams.DreamService; -import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.EventLog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; -import com.android.systemui.EventLogTags; import com.android.systemui.ForegroundServiceController; -import com.android.systemui.UiOffloadThread; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AmbientPulseManager; @@ -101,7 +93,6 @@ public class NotificationEntryManager implements Dependency.get(NotificationGroupManager.class); private final NotificationGutsManager mGutsManager = Dependency.get(NotificationGutsManager.class); - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final DeviceProvisionedController mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); private final VisualStabilityManager mVisualStabilityManager = @@ -124,7 +115,6 @@ public class NotificationEntryManager implements private final Handler mDeferredNotificationViewUpdateHandler; private Runnable mUpdateNotificationViewsCallback; - protected IDreamManager mDreamManager; protected IStatusBarService mBarService; private NotificationPresenter mPresenter; private Callback mCallback; @@ -136,7 +126,7 @@ public class NotificationEntryManager implements @VisibleForTesting final ArrayList mNotificationLifetimeExtenders = new ArrayList<>(); - @Nullable private AlertTransferListener mAlertTransferListener; + private final List mNotificationEntryListeners = new ArrayList<>(); private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedListener = @@ -169,15 +159,14 @@ public class NotificationEntryManager implements mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.checkService(DreamService.DREAM_SERVICE)); mBubbleController.setDismissListener(this /* bubbleEventListener */); mNotificationData = new NotificationData(); mDeferredNotificationViewUpdateHandler = new Handler(); } - public void setAlertTransferListener(AlertTransferListener listener) { - mAlertTransferListener = listener; + /** Adds a {@link NotificationEntryListener}. */ + public void addNotificationEntryListener(NotificationEntryListener listener) { + mNotificationEntryListeners.add(listener); } /** @@ -261,14 +250,6 @@ public class NotificationEntryManager implements updateNotifications(); } - private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) { - if (mPresenter.isDeviceInVrMode()) { - return true; - } - - return entry.shouldSuppressFullScreenIntent(); - } - public void performRemoveNotification(StatusBarNotification n) { final int rank = mNotificationData.getRank(n.getKey()); final int count = mNotificationData.getActiveNotifications().size(); @@ -413,8 +394,8 @@ public class NotificationEntryManager implements mVisualStabilityManager.onLowPriorityUpdated(entry); mPresenter.updateNotificationViews(); } - if (mAlertTransferListener != null) { - mAlertTransferListener.onEntryReinflated(entry); + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onEntryReinflated(entry); } } } @@ -431,8 +412,10 @@ public class NotificationEntryManager implements final NotificationData.Entry entry = mNotificationData.get(key); abortExistingInflation(key); - if (mAlertTransferListener != null && entry != null) { - mAlertTransferListener.onEntryRemoved(entry); + if (entry != null) { + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onEntryRemoved(entry); + } } // Attempt to remove notifications from their alert managers (heads up, ambient pulse). @@ -589,51 +572,15 @@ public class NotificationEntryManager implements mNotificationData.updateRanking(rankingMap); NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); rankingMap.getRanking(key, ranking); - NotificationData.Entry shadeEntry = createNotificationEntry(notification, ranking); - boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(shadeEntry); - if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { - if (shouldSuppressFullScreenIntent(shadeEntry)) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); - } - } else if (mNotificationData.getImportance(key) - < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: not important enough: " - + key); - } - } else { - // Stop screensaver if the notification has a fullscreen intent. - // (like an incoming phone call) - Dependency.get(UiOffloadThread.class).submit(() -> { - try { - mDreamManager.awaken(); - } catch (RemoteException e) { - e.printStackTrace(); - } - }); - - // not immersive & a fullscreen alert should be shown - if (DEBUG) - Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - try { - EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, - key); - notification.getNotification().fullScreenIntent.send(); - shadeEntry.notifyFullScreenIntentLaunched(); - mMetricsLogger.count("note_fullscreen", 1); - } catch (PendingIntent.CanceledException e) { - } - } - } + NotificationData.Entry entry = createNotificationEntry(notification, ranking); abortExistingInflation(key); mForegroundServiceController.addNotification(notification, mNotificationData.getImportance(key)); - mPendingNotifications.put(key, shadeEntry); - if (mAlertTransferListener != null) { - mAlertTransferListener.onPendingEntryAdded(shadeEntry); + mPendingNotifications.put(key, entry); + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onPendingEntryAdded(entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 6732bbeba4933..b1f74c8ab91e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -29,8 +29,8 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; @@ -95,7 +95,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // not being up to date. mEntryManager = entryManager; - mEntryManager.setAlertTransferListener(mAlertTransferListener); + mEntryManager.addNotificationEntryListener(mNotificationEntryListener); groupManager.addOnGroupChangeListener(mOnGroupChangeListener); } @@ -186,7 +186,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } } - private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() { + private final NotificationEntryListener mNotificationEntryListener = + new NotificationEntryListener() { // Called when a new notification has been posted but is not inflated yet. We use this to // see as early as we can if we need to abort a transfer. @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index c93d151b0f52d..8d1b911b101f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -24,6 +24,7 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Context; @@ -33,15 +34,21 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import android.view.RemoteAnimationAdapter; +import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; +import com.android.systemui.EventLogTags; +import com.android.systemui.UiOffloadThread; import com.android.systemui.assist.AssistManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.CommandQueue; @@ -54,7 +61,9 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -66,6 +75,7 @@ import com.android.systemui.statusbar.policy.PreviewInflater; public class StatusBarNotificationActivityStarter implements NotificationActivityStarter { private static final String TAG = "NotificationClickHandler"; + protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final AssistManager mAssistManager = Dependency.get(AssistManager.class); private final NotificationGroupManager mGroupManager = @@ -84,6 +94,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit Dependency.get(NotificationEntryManager.class); private final StatusBarStateController mStatusBarStateController = Dependency.get(StatusBarStateController.class); + private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = + Dependency.get(NotificationInterruptionStateProvider.class); + private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final Context mContext; private final NotificationPanelView mNotificationPanel; @@ -94,6 +107,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final ActivityLaunchAnimator mActivityLaunchAnimator; private final IStatusBarService mBarService; private final CommandQueue mCommandQueue; + private final IDreamManager mDreamManager; private boolean mIsCollapsingToShowActivityOverLockscreen; @@ -112,6 +126,15 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mCommandQueue = getComponent(context, CommandQueue.class); + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.checkService(DreamService.DREAM_SERVICE)); + + mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationData.Entry entry) { + handleFullScreenIntent(entry); + } + }); } /** @@ -322,6 +345,45 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit }, null, false /* afterKeyguardGone */); } + private void handleFullScreenIntent(NotificationData.Entry entry) { + boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry); + if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) { + if (shouldSuppressFullScreenIntent(entry)) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.key); + } + } else if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.key); + } + } else { + // Stop screensaver if the notification has a fullscreen intent. + // (like an incoming phone call) + Dependency.get(UiOffloadThread.class).submit(() -> { + try { + mDreamManager.awaken(); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + + // not immersive & a fullscreen alert should be shown + if (DEBUG) { + Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); + } + try { + EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, + entry.key); + entry.notification.getNotification().fullScreenIntent.send(); + entry.notifyFullScreenIntentLaunched(); + mMetricsLogger.count("note_fullscreen", 1); + } catch (PendingIntent.CanceledException e) { + // ignore + } + } + } + } + @Override public boolean isCollapsingToShowActivityOverLockscreen() { return mIsCollapsingToShowActivityOverLockscreen; @@ -351,6 +413,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit || !mActivityLaunchAnimator.isAnimationPending(); } + private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) { + if (mPresenter.isDeviceInVrMode()) { + return true; + } + + return entry.shouldSuppressFullScreenIntent(); + } + private void removeNotification(StatusBarNotification notification) { // We have to post it to the UI thread for synchronization Dependency.get(MAIN_HANDLER).post(() -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index e65e806da93a8..b4f99c4ed1566 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -126,7 +126,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mPowerManager = new PowerManager(mContext, powerManagerService, Handler.createAsync(Looper.myLooper())); - mEntryManager = new TestableNotificationEntryManager(mDreamManager, mPowerManager, + mEntryManager = new TestableNotificationEntryManager(mPowerManager, mContext); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); Dependency.get(InitController.class).executePostInitTasks(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index ee39e10b41655..490288efd40fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -32,9 +32,9 @@ import android.testing.TestableLooper; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.AmbientPulseManager; -import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -61,8 +61,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { private AmbientPulseManager mAmbientPulseManager; private HeadsUpManager mHeadsUpManager; @Mock private NotificationEntryManager mNotificationEntryManager; - @Captor private ArgumentCaptor mListenerCaptor; - private AlertTransferListener mAlertTransferListener; + @Captor + private ArgumentCaptor mListenerCaptor; + private NotificationEntryListener mNotificationEntryListener; private final HashMap mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = new NotificationGroupTestHelper(mContext); @@ -85,8 +86,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); - verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture()); - mAlertTransferListener = mListenerCaptor.getValue(); + verify(mNotificationEntryManager).addNotificationEntryListener(mListenerCaptor.capture()); + mNotificationEntryListener = mListenerCaptor.getValue(); mHeadsUpManager.addListener(mGroupAlertTransferHelper); mAmbientPulseManager.addListener(mGroupAlertTransferHelper); } @@ -121,7 +122,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mAlertTransferListener.onPendingEntryAdded(childEntry2); + mNotificationEntryListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // The alert state should transfer back to the summary as there is now more than one @@ -148,7 +149,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mAlertTransferListener.onPendingEntryAdded(childEntry2); + mNotificationEntryListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Dozing changed so no reason to re-alert summary. @@ -186,7 +187,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mAlertTransferListener.onEntryReinflated(childEntry); + mNotificationEntryListener.onEntryReinflated(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key)); @@ -210,13 +211,13 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mAlertTransferListener.onPendingEntryAdded(childEntry2); + mNotificationEntryListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mAlertTransferListener.onEntryReinflated(childEntry); + mNotificationEntryListener.onEntryReinflated(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -236,7 +237,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - mAlertTransferListener.onEntryRemoved(childEntry); + mNotificationEntryListener.onEntryRemoved(childEntry); assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 39c420388f783..acd6b536129e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -223,7 +223,7 @@ public class StatusBarTest extends SysuiTestCase { mNotificationInterruptionStateProvider.setUpWithPresenter(mNotificationPresenter, mHeadsUpManager, mHeadsUpSuppressor); - mEntryManager = new TestableNotificationEntryManager(mDreamManager, mPowerManager, mContext); + mEntryManager = new TestableNotificationEntryManager(mPowerManager, mContext); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, mKeyguardIndicationController, mStackScroller, mHeadsUpManager, @@ -738,10 +738,8 @@ public class StatusBarTest extends SysuiTestCase { public static class TestableNotificationEntryManager extends NotificationEntryManager { - public TestableNotificationEntryManager(IDreamManager dreamManager, - PowerManager powerManager, Context context) { + public TestableNotificationEntryManager(PowerManager powerManager, Context context) { super(context); - mDreamManager = dreamManager; mPowerManager = powerManager; } From 26bc59b506a19e4f4c4b43f5d06b8e0716968ac4 Mon Sep 17 00:00:00 2001 From: Gus Prevas Date: Wed, 19 Dec 2018 11:26:39 -0500 Subject: [PATCH 3/3] Combines NotificationEntryManager listener interfaces (part 1). This change combines the NotificationEntryManager.Callback interface with the NotificationEntryListener interface. Future changes will reduce duplication between the methods in these interfaces, move the previous Callback instance into the list of listeners instead of having its own special field, and introduce more listeners to replace logic that's proactively invoked in NotificationEntryManager, but this first step is just a pure combination of the two interfaces. Test: atest SystemUITests, manual Change-Id: Ifafcee697e7dd35e0aaae32f677ef1582f8d5920 --- .../statusbar/NotificationPresenter.java | 2 - .../NotificationEntryListener.java | 33 ++++++++++++++ .../NotificationEntryManager.java | 39 +---------------- .../phone/StatusBarNotificationPresenter.java | 43 ++++++++++++------- .../statusbar/NonPhoneDependencyTest.java | 4 +- .../NotificationEntryManagerTest.java | 3 +- .../statusbar/phone/StatusBarTest.java | 6 ++- 7 files changed, 72 insertions(+), 58 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index ee0d1a2d5a51b..c945afd1cb39c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -29,7 +28,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow */ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpandClickListener, ActivatableNotificationView.OnActivatedListener, - NotificationEntryManager.Callback, NotificationRowBinder.BindRowCallback { /** * Returns true if the presenter is not visible. For example, it may not be necessary to do diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index 4721994fe91da..361ae8b1920ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar.notification; +import android.service.notification.StatusBarNotification; + /** * Listener interface for changes sent by NotificationEntryManager. */ @@ -26,6 +28,18 @@ public interface NotificationEntryListener { default void onPendingEntryAdded(NotificationData.Entry entry) { } + /** + * Called when a new entry is created. + */ + default void onNotificationAdded(NotificationData.Entry entry) { + } + + /** + * Called when a notification was updated. + */ + default void onNotificationUpdated(StatusBarNotification notification) { + } + /** * Called when an existing notification's views are reinflated (usually due to an update being * posted to that notification). @@ -36,7 +50,26 @@ public interface NotificationEntryListener { /** * Called when a notification has been removed (either because the user swiped it away or * because the developer retracted it). + * + * TODO: combine this with onNotificationRemoved(). */ default void onEntryRemoved(NotificationData.Entry entry) { } + + /** + * Called when a notification was removed. + * + * @param key key of notification that was removed + * @param old StatusBarNotification of the notification before it was removed + */ + default void onNotificationRemoved(String key, StatusBarNotification old) { + } + + /** + * Removes a notification immediately. + * + * TODO: combine this with onNotificationRemoved(). + */ + default void onPerformRemoveNotification(StatusBarNotification statusBarNotification) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index fdc5363168728..98ddd6b9cc5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -117,7 +117,7 @@ public class NotificationEntryManager implements protected IStatusBarService mBarService; private NotificationPresenter mPresenter; - private Callback mCallback; + private NotificationEntryListener mCallback; protected PowerManager mPowerManager; private NotificationListenerService.RankingMap mLatestRankingMap; protected HeadsUpManager mHeadsUpManager; @@ -208,7 +208,7 @@ public class NotificationEntryManager implements } public void setUpWithPresenter(NotificationPresenter presenter, - NotificationListContainer listContainer, Callback callback, + NotificationListContainer listContainer, NotificationEntryListener callback, HeadsUpManager headsUpManager) { mPresenter = presenter; mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews; @@ -784,39 +784,4 @@ public class NotificationEntryManager implements public Iterable getPendingNotificationsIterator() { return mPendingNotifications.values(); } - - /** - * Callback for NotificationEntryManager. - */ - public interface Callback { - - /** - * Called when a new entry is created. - * - * @param shadeEntry entry that was created - */ - void onNotificationAdded(NotificationData.Entry shadeEntry); - - /** - * Called when a notification was updated. - * - * @param notification notification that was updated - */ - void onNotificationUpdated(StatusBarNotification notification); - - /** - * Called when a notification was removed. - * - * @param key key of notification that was removed - * @param old StatusBarNotification of the notification before it was removed - */ - void onNotificationRemoved(String key, StatusBarNotification old); - - /** - * Removes a notification immediately. - * - * @param statusBarNotification notification that is being removed - */ - void onPerformRemoveNotification(StatusBarNotification statusBarNotification); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index e69dea58f2fed..d643f07e28597 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationRowBinder; @@ -172,8 +173,33 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller; Dependency.get(InitController.class).addPostInitTask(() -> { + NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { + @Override + public void onNotificationAdded(Entry entry) { + // Recalculate the position of the sliding windows and the titles. + mShadeController.updateAreThereNotifications(); + } + + @Override + public void onNotificationUpdated(StatusBarNotification notification) { + mShadeController.updateAreThereNotifications(); + } + + @Override + public void onNotificationRemoved(String key, StatusBarNotification old) { + StatusBarNotificationPresenter.this.onNotificationRemoved(key, old); + } + + @Override + public void onPerformRemoveNotification( + StatusBarNotification statusBarNotification) { + StatusBarNotificationPresenter.this.onPerformRemoveNotification(); + } + }; + mViewHierarchyManager.setUpWithPresenter(this, notifListContainer); - mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager); + mEntryManager.setUpWithPresenter( + this, notifListContainer, notificationEntryListener, mHeadsUpManager); mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, mEntryManager, this); mNotificationInterruptionStateProvider.setUpWithPresenter( @@ -227,8 +253,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, || mActivityLaunchAnimator.isAnimationRunning(); } - @Override - public void onPerformRemoveNotification(StatusBarNotification n) { + private void onPerformRemoveNotification() { if (mNotificationPanel.hasPulsingNotifications() && !mAmbientPulseManager.hasNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. @@ -254,18 +279,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.updateNotificationViews(); } - @Override - public void onNotificationAdded(Entry shadeEntry) { - // Recalculate the position of the sliding windows and the titles. - mShadeController.updateAreThereNotifications(); - } - - @Override - public void onNotificationUpdated(StatusBarNotification notification) { - mShadeController.updateAreThereNotifications(); - } - - @Override public void onNotificationRemoved(String key, StatusBarNotification old) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index f8ff583162cf9..894ef3dde7792 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java @@ -26,6 +26,7 @@ import android.testing.TestableLooper; import com.android.systemui.Dependency; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -55,7 +56,8 @@ import org.mockito.MockitoAnnotations; public class NonPhoneDependencyTest extends SysuiTestCase { @Mock private NotificationPresenter mPresenter; @Mock private NotificationListContainer mListContainer; - @Mock private NotificationEntryManager.Callback mEntryManagerCallback; + @Mock + private NotificationEntryListener mEntryManagerCallback; @Mock private HeadsUpManager mHeadsUpManager; @Mock private RemoteInputController.Delegate mDelegate; @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback; 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 768ba8a32f58d..701ea7d3e2fe4 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 @@ -103,7 +103,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private KeyguardEnvironment mEnvironment; @Mock private ExpandableNotificationRow mRow; @Mock private NotificationListContainer mListContainer; - @Mock private NotificationEntryManager.Callback mCallback; + @Mock + private NotificationEntryListener mCallback; @Mock private NotificationRowBinder.BindRowCallback mBindCallback; @Mock private HeadsUpManager mHeadsUpManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index acd6b536129e3..c584d026f62be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -92,6 +92,7 @@ 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.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; @@ -146,7 +147,8 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private NotificationPresenter mNotificationPresenter; - @Mock private NotificationEntryManager.Callback mCallback; + @Mock + private NotificationEntryListener mCallback; @Mock private BubbleController mBubbleController; @Mock private NotificationFilter mNotificationFilter; @@ -745,7 +747,7 @@ public class StatusBarTest extends SysuiTestCase { public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, - Callback callback, + NotificationEntryListener callback, HeadsUpManagerPhone headsUpManager, NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);