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