| /* |
| * Copyright (C) 2020 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.interruption; |
| |
| import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; |
| |
| import android.app.Notification; |
| import android.service.notification.StatusBarNotification; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.systemui.dagger.SysUISingleton; |
| import com.android.systemui.plugins.statusbar.StatusBarStateController; |
| import com.android.systemui.statusbar.NotificationListener; |
| import com.android.systemui.statusbar.NotificationRemoteInputManager; |
| import com.android.systemui.statusbar.notification.NotificationEntryManager; |
| import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
| import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; |
| import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; |
| import com.android.systemui.statusbar.policy.HeadsUpManager; |
| import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; |
| |
| import javax.inject.Inject; |
| |
| /** |
| * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager} |
| * entry events and appropriately binds or unbinds the heads up view and promotes it to the top |
| * of the screen. |
| */ |
| @SysUISingleton |
| public class HeadsUpController { |
| private final HeadsUpViewBinder mHeadsUpViewBinder; |
| private final NotificationInterruptStateProvider mInterruptStateProvider; |
| private final NotificationRemoteInputManager mRemoteInputManager; |
| private final VisualStabilityManager mVisualStabilityManager; |
| private final StatusBarStateController mStatusBarStateController; |
| private final NotificationListener mNotificationListener; |
| private final HeadsUpManager mHeadsUpManager; |
| |
| @Inject |
| HeadsUpController( |
| HeadsUpViewBinder headsUpViewBinder, |
| NotificationInterruptStateProvider notificationInterruptStateProvider, |
| HeadsUpManager headsUpManager, |
| NotificationRemoteInputManager remoteInputManager, |
| StatusBarStateController statusBarStateController, |
| VisualStabilityManager visualStabilityManager, |
| NotificationListener notificationListener) { |
| mHeadsUpViewBinder = headsUpViewBinder; |
| mHeadsUpManager = headsUpManager; |
| mInterruptStateProvider = notificationInterruptStateProvider; |
| mRemoteInputManager = remoteInputManager; |
| mStatusBarStateController = statusBarStateController; |
| mVisualStabilityManager = visualStabilityManager; |
| mNotificationListener = notificationListener; |
| } |
| |
| /** |
| * Attach this controller and add its listeners. |
| */ |
| public void attach( |
| NotificationEntryManager entryManager, |
| HeadsUpManager headsUpManager) { |
| entryManager.addCollectionListener(mCollectionListener); |
| headsUpManager.addListener(mOnHeadsUpChangedListener); |
| } |
| |
| private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { |
| @Override |
| public void onEntryAdded(NotificationEntry entry) { |
| if (mInterruptStateProvider.shouldHeadsUp(entry)) { |
| mHeadsUpViewBinder.bindHeadsUpView( |
| entry, HeadsUpController.this::showAlertingView); |
| } |
| } |
| |
| @Override |
| public void onEntryUpdated(NotificationEntry entry) { |
| updateHunState(entry); |
| } |
| |
| @Override |
| public void onEntryRemoved(NotificationEntry entry, int reason) { |
| stopAlerting(entry); |
| } |
| |
| @Override |
| public void onEntryCleanUp(NotificationEntry entry) { |
| mHeadsUpViewBinder.abortBindCallback(entry); |
| } |
| }; |
| |
| /** |
| * Adds the entry to the HUN manager and show it for the first time. |
| */ |
| private void showAlertingView(NotificationEntry entry) { |
| mHeadsUpManager.showNotification(entry); |
| if (!mStatusBarStateController.isDozing()) { |
| // Mark as seen immediately |
| setNotificationShown(entry.getSbn()); |
| } |
| } |
| |
| private void updateHunState(NotificationEntry entry) { |
| boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); |
| // includes check for whether this notification should be filtered: |
| boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry); |
| final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); |
| if (wasHeadsUp) { |
| if (shouldHeadsUp) { |
| mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); |
| } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { |
| // We don't want this to be interrupting anymore, let's remove it |
| mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */); |
| } |
| } else if (shouldHeadsUp && hunAgain) { |
| mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification); |
| } |
| } |
| |
| private void setNotificationShown(StatusBarNotification n) { |
| try { |
| mNotificationListener.setNotificationsShown(new String[]{n.getKey()}); |
| } catch (RuntimeException e) { |
| Log.d(TAG, "failed setNotificationsShown: ", e); |
| } |
| } |
| |
| private void stopAlerting(NotificationEntry entry) { |
| // Attempt to remove notifications from their HUN manager. |
| // Though the remove itself may fail, it lets the manager know to remove as soon as |
| // possible. |
| String key = entry.getKey(); |
| if (mHeadsUpManager.isAlerting(key)) { |
| // A cancel() in response to a remote input shouldn't be delayed, as it makes the |
| // sending look longer than it takes. |
| // Also we should not defer the removal if reordering isn't allowed since otherwise |
| // some notifications can't disappear before the panel is closed. |
| boolean ignoreEarliestRemovalTime = |
| mRemoteInputManager.getController().isSpinning(key) |
| && !FORCE_REMOTE_INPUT_HISTORY |
| || !mVisualStabilityManager.isReorderingAllowed(); |
| mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); |
| } |
| } |
| |
| /** |
| * Checks whether an update for a notification warrants an alert for the user. |
| * |
| * @param oldEntry the entry for this notification. |
| * @param newNotification the new notification for this entry. |
| * @return whether this notification should alert the user. |
| */ |
| public static boolean alertAgain( |
| NotificationEntry oldEntry, Notification newNotification) { |
| return oldEntry == null || !oldEntry.hasInterrupted() |
| || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; |
| } |
| |
| private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { |
| @Override |
| public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { |
| if (!isHeadsUp && !entry.getRow().isRemoved()) { |
| mHeadsUpViewBinder.unbindHeadsUpView(entry); |
| } |
| } |
| }; |
| |
| private static final String TAG = "HeadsUpBindController"; |
| } |