Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b43dc656 authored by Gus Prevas's avatar Gus Prevas
Browse files

Combines NotificationEntryManager listener interfaces (part 2).

This change introduces the NotificationAlertingManager component, a
NotificationEntryListener which handles showing/hiding/updating alerts
(heads-up or ambient pulsing) in response to events from the
NotificationEntryManager.  All code in the new component was moved out
of NotificationEntryManager proper.

Test: atest SystemUITests, manual
Change-Id: I46a046da6caf39b1d314b357e21ac6b4755c5796
parent ab1ad60e
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
@@ -266,6 +267,8 @@ public class Dependency extends SystemUI {
    @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
    @Inject Lazy<BubbleController> mBubbleController;
    @Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
    @Inject
    Lazy<NotificationAlertingManager> mNotificationAlertingManager;
    @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
    @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
    @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
@@ -439,6 +442,7 @@ public class Dependency extends SystemUI {
                mRemoteInputQuickSettingsDisabler::get);
        mProviders.put(BubbleController.class, mBubbleController::get);
        mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
        mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);

        sDependency = this;
    }
+182 −0
Original line number Diff line number Diff line
/*
 * 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.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
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;

import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.Log;

import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;

import javax.inject.Inject;
import javax.inject.Singleton;

import dagger.Lazy;

/** Handles heads-up and pulsing behavior driven by notification changes. */
@Singleton
public class NotificationAlertingManager {

    private static final String TAG = "NotifAlertManager";

    private final AmbientPulseManager mAmbientPulseManager;
    private final NotificationRemoteInputManager mRemoteInputManager;
    private final VisualStabilityManager mVisualStabilityManager;
    private final Lazy<ShadeController> mShadeController;
    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
    private final NotificationListener mNotificationListener;

    private HeadsUpManager mHeadsUpManager;

    @Inject
    public NotificationAlertingManager(
            NotificationEntryManager notificationEntryManager,
            AmbientPulseManager ambientPulseManager,
            NotificationRemoteInputManager remoteInputManager,
            VisualStabilityManager visualStabilityManager,
            Lazy<ShadeController> shadeController,
            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
            NotificationListener notificationListener) {
        mAmbientPulseManager = ambientPulseManager;
        mRemoteInputManager = remoteInputManager;
        mVisualStabilityManager = visualStabilityManager;
        mShadeController = shadeController;
        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
        mNotificationListener = notificationListener;

        notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
            @Override
            public void onEntryInflated(NotificationData.Entry entry, int inflatedFlags) {
                showAlertingView(entry, inflatedFlags);
            }

            @Override
            public void onEntryUpdated(NotificationData.Entry entry) {
                updateAlertState(entry);
            }

            @Override
            public void onEntryRemoved(NotificationData.Entry entry) {
                stopAlerting(entry);
            }
        });
    }

    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
        mHeadsUpManager = headsUpManager;
    }

    /**
     * Adds the entry to the respective alerting manager if the content view was inflated and
     * the entry should still alert.
     *
     * @param entry         entry to add
     * @param inflatedFlags flags representing content views that were inflated
     */
    private void showAlertingView(NotificationData.Entry entry,
            @NotificationInflater.InflationFlag int inflatedFlags) {
        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 (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
                mHeadsUpManager.showNotification(entry);
                // Mark as seen immediately
                setNotificationShown(entry.notification);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
            }
        }
        if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) {
            if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
                mAmbientPulseManager.showNotification(entry);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT);
            }
        }
    }

    private void updateAlertState(NotificationData.Entry entry) {
        boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
        AlertingNotificationManager alertManager;
        boolean shouldAlert;
        if (mShadeController.get().isDozing()) {
            alertManager = mAmbientPulseManager;
            shouldAlert = mNotificationInterruptionStateProvider.shouldPulse(entry);
        } else {
            alertManager = mHeadsUpManager;
            shouldAlert = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
        }
        final boolean wasAlerting = alertManager.isAlerting(entry.key);
        if (wasAlerting) {
            if (!shouldAlert) {
                // We don't want this to be interrupting anymore, let's remove it
                alertManager.removeNotification(entry.key,
                        false /* ignoreEarliestRemovalTime */);
            } else {
                alertManager.updateNotification(entry.key, alertAgain);
            }
        } else if (shouldAlert && alertAgain) {
            // This notification was updated to be alerting, show it!
            alertManager.showNotification(entry);
        }
    }

    private static boolean alertAgain(
            NotificationData.Entry oldEntry, Notification newNotification) {
        return oldEntry == null || !oldEntry.hasInterrupted()
                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
    }

    private void setNotificationShown(StatusBarNotification n) {
        try {
            mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
        } catch (RuntimeException e) {
            Log.d(TAG, "failed setNotificationsShown: ", e);
        }
    }

    private void stopAlerting(NotificationData.Entry entry) {
        // Attempt to remove notifications from their alert managers (heads up, ambient pulse).
        // Though the remove itself may fail, it lets the manager know to remove as soon as
        // possible.
        if (mHeadsUpManager.isAlerting(entry.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(entry.key)
                            && !FORCE_REMOTE_INPUT_HISTORY
                            || !mVisualStabilityManager.isReorderingAllowed();
            mHeadsUpManager.removeNotification(entry.key, ignoreEarliestRemovalTime);
        }
        if (mAmbientPulseManager.isAlerting(entry.key)) {
            mAmbientPulseManager.removeNotification(entry.key,
                    false /* ignoreEarliestRemovalTime */);
        }
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification;

import android.service.notification.StatusBarNotification;

import com.android.systemui.statusbar.notification.row.NotificationInflater;

/**
 * Listener interface for changes sent by NotificationEntryManager.
 */
@@ -37,7 +39,14 @@ public interface NotificationEntryListener {
    /**
     * Called when a notification was updated.
     */
    default void onNotificationUpdated(StatusBarNotification notification) {
    default void onEntryUpdated(NotificationData.Entry entry) {
    }

    /**
     * Called when a notification's views are inflated for the first time.
     */
    default void onEntryInflated(NotificationData.Entry entry,
            @NotificationInflater.InflationFlag int inflatedFlags) {
    }

    /**
+7 −120
Original line number Diff line number Diff line
@@ -16,9 +16,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.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;

import android.annotation.Nullable;
import android.app.Notification;
@@ -42,10 +39,8 @@ import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -58,7 +53,6 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
@@ -108,8 +102,6 @@ public class NotificationEntryManager implements
    // Lazily retrieved dependencies
    private NotificationRemoteInputManager mRemoteInputManager;
    private NotificationMediaManager mMediaManager;
    private NotificationListener mNotificationListener;
    private ShadeController mShadeController;
    private NotificationRowBinder mNotificationRowBinder;

    private final Handler mDeferredNotificationViewUpdateHandler;
@@ -186,20 +178,6 @@ public class NotificationEntryManager implements
        return mMediaManager;
    }

    private NotificationListener getNotificationListener() {
        if (mNotificationListener == null) {
            mNotificationListener = Dependency.get(NotificationListener.class);
        }
        return mNotificationListener;
    }

    private ShadeController getShadeController() {
        if (mShadeController == null) {
            mShadeController = Dependency.get(ShadeController.class);
        }
        return mShadeController;
    }

    private NotificationRowBinder getRowBinder() {
        if (mNotificationRowBinder == null) {
            mNotificationRowBinder = Dependency.get(NotificationRowBinder.class);
@@ -349,35 +327,6 @@ public class NotificationEntryManager implements
        }
    }

    /**
     * Adds the entry to the respective alerting manager if the content view was inflated and
     * the entry should still alert.
     *
     * @param entry entry to add
     * @param inflatedFlags flags representing content views that were inflated
     */
    private void showAlertingView(NotificationData.Entry entry,
            @InflationFlag int inflatedFlags) {
        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 (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
                mHeadsUpManager.showNotification(entry);
                // Mark as seen immediately
                setNotificationShown(entry.notification);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
            }
        }
        if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) {
            if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
                mAmbientPulseManager.showNotification(entry);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT);
            }
        }
    }

    @Override
    public void onAsyncInflationFinished(NotificationData.Entry entry,
            @InflationFlag int inflatedFlags) {
@@ -387,7 +336,9 @@ public class NotificationEntryManager implements
        if (!entry.isRowRemoved()) {
            boolean isNew = mNotificationData.get(entry.key) == null;
            if (isNew) {
                showAlertingView(entry, inflatedFlags);
                for (NotificationEntryListener listener : mNotificationEntryListeners) {
                    listener.onEntryInflated(entry, inflatedFlags);
                }
                addEntry(entry);
            } else {
                if (entry.getRow().hasLowPriorityStateUpdated()) {
@@ -418,23 +369,6 @@ public class NotificationEntryManager implements
            }
        }

        // Attempt to remove notifications from their alert managers (heads up, ambient pulse).
        // Though the remove itself may fail, it lets the manager know to remove as soon as
        // possible.
        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 = getRemoteInputManager().getController().isSpinning(key)
                    && !FORCE_REMOTE_INPUT_HISTORY
                    || !mVisualStabilityManager.isReorderingAllowed();
            mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
        }
        if (mAmbientPulseManager.isAlerting(key)) {
            mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
        }

        if (entry == null) {
            mCallback.onNotificationRemoved(key, null /* old */);
            return;
@@ -616,11 +550,6 @@ public class NotificationEntryManager implements
        }
    }

    private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
        return oldEntry == null || !oldEntry.hasInterrupted()
                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
    }

    private void updateNotificationInternal(StatusBarNotification notification,
            NotificationListenerService.RankingMap ranking) throws InflationException {
        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
@@ -651,14 +580,6 @@ public class NotificationEntryManager implements
        mForegroundServiceController.updateNotification(notification,
                mNotificationData.getImportance(key));

        boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
        if (getShadeController().isDozing()) {
            updateAlertState(entry, mNotificationInterruptionStateProvider.shouldPulse(entry),
                    alertAgain, mAmbientPulseManager);
        } else {
            updateAlertState(entry, mNotificationInterruptionStateProvider.shouldHeadsUp(entry),
                    alertAgain, mHeadsUpManager);
        }
        updateNotifications();

        if (!notification.isClearable()) {
@@ -674,7 +595,10 @@ public class NotificationEntryManager implements
            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
        }

        mCallback.onNotificationUpdated(notification);
        for (NotificationEntryListener listener : mNotificationEntryListeners) {
            listener.onEntryUpdated(entry);
        }
        mCallback.onEntryUpdated(entry);

        maybeScheduleUpdateNotificationViews(entry);
    }
@@ -739,43 +663,6 @@ public class NotificationEntryManager implements
        }
    }

    private void setNotificationShown(StatusBarNotification n) {
        setNotificationsShown(new String[]{n.getKey()});
    }

    protected void setNotificationsShown(String[] keys) {
        try {
            getNotificationListener().setNotificationsShown(keys);
        } catch (RuntimeException e) {
            Log.d(TAG, "failed setNotificationsShown: ", e);
        }
    }

    /**
     * Update the entry's alert state and call the appropriate {@link AlertingNotificationManager}
     * method.
     * @param entry entry to update
     * @param shouldAlert whether or not it should be alerting
     * @param alertAgain whether or not an alert should actually come in as if it were new
     * @param alertManager the alerting notification manager that manages the alert state
     */
    private void updateAlertState(NotificationData.Entry entry, boolean shouldAlert,
            boolean alertAgain, AlertingNotificationManager alertManager) {
        final boolean wasAlerting = alertManager.isAlerting(entry.key);
        if (wasAlerting) {
            if (!shouldAlert) {
                // We don't want this to be interrupting anymore, lets remove it
                alertManager.removeNotification(entry.key,
                        false /* ignoreEarliestRemovalTime */);
            } else {
                alertManager.updateNotification(entry.key, alertAgain);
            }
        } else if (shouldAlert && alertAgain) {
            // This notification was updated to be alerting, show it!
            alertManager.showNotification(entry);
        }
    }

    /**
     * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
     * notifications whose views have not yet been inflated. In general, the system pretends like
+5 −1
Original line number Diff line number Diff line
@@ -187,6 +187,7 @@ import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
@@ -385,6 +386,8 @@ public class StatusBar extends SystemUI implements DemoMode,
    protected AppOpsController mAppOpsController;
    protected KeyguardViewMediator mKeyguardViewMediator;
    private ZenModeController mZenController;
    private final NotificationAlertingManager mNotificationAlertingManager =
            Dependency.get(NotificationAlertingManager.class);

    // for disabling the status bar
    private int mDisabled1 = 0;
@@ -1036,7 +1039,8 @@ public class StatusBar extends SystemUI implements DemoMode,

        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
                mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
                mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager);
                mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager,
                mNotificationAlertingManager);

        mAppOpsController.addCallback(APP_OPS, this);
        mNotificationListener.setUpWithPresenter(mPresenter);
Loading