1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.notification.interruption; 18 19 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; 20 21 import android.app.Notification; 22 import android.service.notification.StatusBarNotification; 23 import android.util.Log; 24 25 import androidx.annotation.NonNull; 26 27 import com.android.systemui.dagger.SysUISingleton; 28 import com.android.systemui.plugins.statusbar.StatusBarStateController; 29 import com.android.systemui.statusbar.NotificationListener; 30 import com.android.systemui.statusbar.NotificationRemoteInputManager; 31 import com.android.systemui.statusbar.notification.NotificationEntryManager; 32 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 33 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; 34 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 35 import com.android.systemui.statusbar.policy.HeadsUpManager; 36 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; 37 38 import javax.inject.Inject; 39 40 /** 41 * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager} 42 * entry events and appropriately binds or unbinds the heads up view and promotes it to the top 43 * of the screen. 44 */ 45 @SysUISingleton 46 public class HeadsUpController { 47 private final HeadsUpViewBinder mHeadsUpViewBinder; 48 private final NotificationInterruptStateProvider mInterruptStateProvider; 49 private final NotificationRemoteInputManager mRemoteInputManager; 50 private final VisualStabilityManager mVisualStabilityManager; 51 private final StatusBarStateController mStatusBarStateController; 52 private final NotificationListener mNotificationListener; 53 private final HeadsUpManager mHeadsUpManager; 54 55 @Inject HeadsUpController( HeadsUpViewBinder headsUpViewBinder, NotificationInterruptStateProvider notificationInterruptStateProvider, HeadsUpManager headsUpManager, NotificationRemoteInputManager remoteInputManager, StatusBarStateController statusBarStateController, VisualStabilityManager visualStabilityManager, NotificationListener notificationListener)56 HeadsUpController( 57 HeadsUpViewBinder headsUpViewBinder, 58 NotificationInterruptStateProvider notificationInterruptStateProvider, 59 HeadsUpManager headsUpManager, 60 NotificationRemoteInputManager remoteInputManager, 61 StatusBarStateController statusBarStateController, 62 VisualStabilityManager visualStabilityManager, 63 NotificationListener notificationListener) { 64 mHeadsUpViewBinder = headsUpViewBinder; 65 mHeadsUpManager = headsUpManager; 66 mInterruptStateProvider = notificationInterruptStateProvider; 67 mRemoteInputManager = remoteInputManager; 68 mStatusBarStateController = statusBarStateController; 69 mVisualStabilityManager = visualStabilityManager; 70 mNotificationListener = notificationListener; 71 } 72 73 /** 74 * Attach this controller and add its listeners. 75 */ attach( NotificationEntryManager entryManager, HeadsUpManager headsUpManager)76 public void attach( 77 NotificationEntryManager entryManager, 78 HeadsUpManager headsUpManager) { 79 entryManager.addCollectionListener(mCollectionListener); 80 headsUpManager.addListener(mOnHeadsUpChangedListener); 81 } 82 83 private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { 84 @Override 85 public void onEntryAdded(NotificationEntry entry) { 86 if (mInterruptStateProvider.shouldHeadsUp(entry)) { 87 mHeadsUpViewBinder.bindHeadsUpView( 88 entry, HeadsUpController.this::showAlertingView); 89 } 90 } 91 92 @Override 93 public void onEntryUpdated(NotificationEntry entry) { 94 updateHunState(entry); 95 } 96 97 @Override 98 public void onEntryRemoved(NotificationEntry entry, int reason) { 99 stopAlerting(entry); 100 } 101 102 @Override 103 public void onEntryCleanUp(NotificationEntry entry) { 104 mHeadsUpViewBinder.abortBindCallback(entry); 105 } 106 }; 107 108 /** 109 * Adds the entry to the HUN manager and show it for the first time. 110 */ showAlertingView(NotificationEntry entry)111 private void showAlertingView(NotificationEntry entry) { 112 mHeadsUpManager.showNotification(entry); 113 if (!mStatusBarStateController.isDozing()) { 114 // Mark as seen immediately 115 setNotificationShown(entry.getSbn()); 116 } 117 } 118 updateHunState(NotificationEntry entry)119 private void updateHunState(NotificationEntry entry) { 120 boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); 121 // includes check for whether this notification should be filtered: 122 boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry); 123 final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); 124 if (wasHeadsUp) { 125 if (shouldHeadsUp) { 126 mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); 127 } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { 128 // We don't want this to be interrupting anymore, let's remove it 129 mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */); 130 } 131 } else if (shouldHeadsUp && hunAgain) { 132 mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification); 133 } 134 } 135 setNotificationShown(StatusBarNotification n)136 private void setNotificationShown(StatusBarNotification n) { 137 try { 138 mNotificationListener.setNotificationsShown(new String[]{n.getKey()}); 139 } catch (RuntimeException e) { 140 Log.d(TAG, "failed setNotificationsShown: ", e); 141 } 142 } 143 stopAlerting(NotificationEntry entry)144 private void stopAlerting(NotificationEntry entry) { 145 // Attempt to remove notifications from their HUN manager. 146 // Though the remove itself may fail, it lets the manager know to remove as soon as 147 // possible. 148 String key = entry.getKey(); 149 if (mHeadsUpManager.isAlerting(key)) { 150 // A cancel() in response to a remote input shouldn't be delayed, as it makes the 151 // sending look longer than it takes. 152 // Also we should not defer the removal if reordering isn't allowed since otherwise 153 // some notifications can't disappear before the panel is closed. 154 boolean ignoreEarliestRemovalTime = 155 mRemoteInputManager.isSpinning(key) 156 && !FORCE_REMOTE_INPUT_HISTORY 157 || !mVisualStabilityManager.isReorderingAllowed(); 158 mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); 159 } 160 } 161 162 /** 163 * Checks whether an update for a notification warrants an alert for the user. 164 * 165 * @param oldEntry the entry for this notification. 166 * @param newNotification the new notification for this entry. 167 * @return whether this notification should alert the user. 168 */ alertAgain( NotificationEntry oldEntry, Notification newNotification)169 public static boolean alertAgain( 170 NotificationEntry oldEntry, Notification newNotification) { 171 return oldEntry == null || !oldEntry.hasInterrupted() 172 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 173 } 174 175 private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { 176 @Override 177 public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { 178 if (!isHeadsUp && !entry.getRow().isRemoved()) { 179 mHeadsUpViewBinder.unbindHeadsUpView(entry); 180 } 181 } 182 }; 183 184 private static final String TAG = "HeadsUpBindController"; 185 } 186