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

Commit 9b5ba515 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Convert HeadsUpCoordinator/Test to Kotlin"

parents 96e8db5b f3ab1c4a
Loading
Loading
Loading
Loading
+200 −0
Original line number Diff line number Diff line
@@ -13,109 +13,77 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.notification.collection.coordinator;

import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;

import android.util.ArraySet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.dagger.IncomingHeader;
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.concurrency.DelayableExecutor;

import javax.inject.Inject;
package com.android.systemui.statusbar.notification.collection.coordinator

import android.util.ArraySet
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject

/**
 * Coordinates heads up notification (HUN) interactions with the notification pipeline based on
 * the HUN state reported by the {@link HeadsUpManager}. In this class we only consider one
 * notification, in particular the {@link HeadsUpManager#getTopEntry()}, to be HeadsUpping at a
 * the HUN state reported by the [HeadsUpManager]. In this class we only consider one
 * notification, in particular the [HeadsUpManager.getTopEntry], to be HeadsUpping at a
 * time even though other notifications may be queued to heads up next.
 *
 * The current HUN, but not HUNs that are queued to heads up, will be:
 * - Lifetime extended until it's no longer heads upping.
 * - Promoted out of its group if it's a child of a group.
 * - In the HeadsUpCoordinatorSection. Ordering is configured in {@link NotifCoordinators}.
 * - In the HeadsUpCoordinatorSection. Ordering is configured in [NotifCoordinators].
 * - Removed from HeadsUpManager if it's removed from the NotificationCollection.
 *
 * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
 * Note: The inflation callback in [PreparationCoordinator] handles showing HUNs.
 */
@CoordinatorScope
public class HeadsUpCoordinator implements Coordinator {
    private static final String TAG = "HeadsUpCoordinator";
class HeadsUpCoordinator @Inject constructor(
    private val mHeadsUpManager: HeadsUpManager,
    private val mHeadsUpViewBinder: HeadsUpViewBinder,
    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
    private val mRemoteInputManager: NotificationRemoteInputManager,
    @IncomingHeader private val mIncomingHeaderController: NodeController,
    @Main private val mExecutor: DelayableExecutor
) : Coordinator {
    private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null

    private final HeadsUpManager mHeadsUpManager;
    private final HeadsUpViewBinder mHeadsUpViewBinder;
    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
    private final NotificationRemoteInputManager mRemoteInputManager;
    private final NodeController mIncomingHeaderController;
    private final DelayableExecutor mExecutor;

    private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
    // notifs we've extended the lifetime for
    private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>();

    @Inject
    public HeadsUpCoordinator(
            HeadsUpManager headsUpManager,
            HeadsUpViewBinder headsUpViewBinder,
            NotificationInterruptStateProvider notificationInterruptStateProvider,
            NotificationRemoteInputManager remoteInputManager,
            @IncomingHeader NodeController incomingHeaderController,
            @Main DelayableExecutor executor) {
        mHeadsUpManager = headsUpManager;
        mHeadsUpViewBinder = headsUpViewBinder;
        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
        mRemoteInputManager = remoteInputManager;
        mIncomingHeaderController = incomingHeaderController;
        mExecutor = executor;
    }
    private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>()

    @Override
    public void attach(NotifPipeline pipeline) {
        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
        pipeline.addCollectionListener(mNotifCollectionListener);
        pipeline.addPromoter(mNotifPromoter);
        pipeline.addNotificationLifetimeExtender(mLifetimeExtender);
    override fun attach(pipeline: NotifPipeline) {
        mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
        pipeline.addCollectionListener(mNotifCollectionListener)
        pipeline.addPromoter(mNotifPromoter)
        pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
    }

    public NotifSectioner getSectioner() {
        return mNotifSectioner;
    private fun onHeadsUpViewBound(entry: NotificationEntry) {
        mHeadsUpManager.showNotification(entry)
    }

    private void onHeadsUpViewBound(NotificationEntry entry) {
        mHeadsUpManager.showNotification(entry);
    }

    private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
    private val mNotifCollectionListener = object : NotifCollectionListener {
        /**
         * Notification was just added and if it should heads up, bind the view and then show it.
         */
        @Override
        public void onEntryAdded(NotificationEntry entry) {
        override fun onEntryAdded(entry: NotificationEntry) {
            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
                mHeadsUpViewBinder.bindHeadsUpView(
                        entry,
                        HeadsUpCoordinator.this::onHeadsUpViewBound);
                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
            }
        }

@@ -124,136 +92,109 @@ public class HeadsUpCoordinator implements Coordinator {
         * heads up, if the notification specified that it only wants to alert once, don't heads
         * up again.
         */
        @Override
        public void onEntryUpdated(NotificationEntry entry) {
            boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
        override fun onEntryUpdated(entry: NotificationEntry) {
            val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification)
            // includes check for whether this notification should be filtered:
            boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry);
            final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
            val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
            val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key)
            if (wasHeadsUp) {
                if (shouldHeadsUp) {
                    mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
                    mHeadsUpManager.updateNotification(entry.key, hunAgain)
                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) {
                    // We don't want this to be interrupting anymore, let's remove it
                    mHeadsUpManager.removeNotification(
                            entry.getKey(), false /* removeImmediately */);
                        entry.key, false /* removeImmediately */
                    )
                }
            } else if (shouldHeadsUp && hunAgain) {
                // This notification was updated to be heads up, show it!
                mHeadsUpViewBinder.bindHeadsUpView(
                        entry,
                        HeadsUpCoordinator.this::onHeadsUpViewBound);
                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
            }
        }

        /**
         * Stop alerting HUNs that are removed from the notification collection
         */
        @Override
        public void onEntryRemoved(NotificationEntry entry, int reason) {
            final String entryKey = entry.getKey();
        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
            val entryKey = entry.key
            if (mHeadsUpManager.isAlerting(entryKey)) {
                boolean removeImmediatelyForRemoteInput =
                        mRemoteInputManager.isSpinning(entryKey)
                                && !FORCE_REMOTE_INPUT_HISTORY;
                mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput);
                val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
                        !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
                mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
            }
        }

        @Override
        public void onEntryCleanUp(NotificationEntry entry) {
            mHeadsUpViewBinder.abortBindCallback(entry);
        override fun onEntryCleanUp(entry: NotificationEntry) {
            mHeadsUpViewBinder.abortBindCallback(entry)
        }
    };

    private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
        @Override
        public @NonNull String getName() {
            return TAG;
    }

        @Override
        public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
            mEndLifetimeExtension = callback;
    private val mLifetimeExtender = object : NotifLifetimeExtender {
        override fun getName() = TAG

        override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
            mEndLifetimeExtension = callback
        }

        @Override
        public boolean maybeExtendLifetime(@NonNull NotificationEntry entry, int reason) {
            boolean extend = !mHeadsUpManager.canRemoveImmediately(entry.getKey());
            if (extend) {
        override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
            if (mHeadsUpManager.canRemoveImmediately(entry.key)) {
                return false
            }
            if (isSticky(entry)) {
                    long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey());
                    mExecutor.executeDelayed(() -> {
                        if (mNotifsExtendingLifetime.contains(entry)
                                && mHeadsUpManager.canRemoveImmediately(entry.getKey())) {
                            mHeadsUpManager.removeNotification(
                                    entry.getKey(), /* releaseImmediately */  true);
                val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
                mExecutor.executeDelayed({
                    val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key)
                    if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) {
                        mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true)
                    }
                    }, removeAfterMillis);
                }, removeAfterMillis)
            } else {
                    // remove as early as possible
                    mExecutor.execute(
                            () -> mHeadsUpManager.removeNotification(
                                    entry.getKey(), /* releaseImmediately */  false));
                mExecutor.execute {
                    mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false)
                }
                mNotifsExtendingLifetime.add(entry);
            }
            return extend;
            mNotifsExtendingLifetime.add(entry)
            return true
        }

        @Override
        public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
            mNotifsExtendingLifetime.remove(entry);
        override fun cancelLifetimeExtension(entry: NotificationEntry) {
            mNotifsExtendingLifetime.remove(entry)
        }
    };

    private final NotifPromoter mNotifPromoter = new NotifPromoter(TAG) {
        @Override
        public boolean shouldPromoteToTopLevel(NotificationEntry entry) {
            return isCurrentlyShowingHun(entry);
    }
    };

    private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp",
            NotificationPriorityBucketKt.BUCKET_HEADS_UP) {
        @Override
        public boolean isInSection(ListEntry entry) {
            return isCurrentlyShowingHun(entry);
    private val mNotifPromoter = object : NotifPromoter(TAG) {
        override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean =
            isCurrentlyShowingHun(entry)
    }

        @Nullable
        @Override
        public NodeController getHeaderNodeController() {
    val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
        override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry)

        override fun getHeaderNodeController(): NodeController? =
            // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
            if (RankingCoordinator.SHOW_ALL_SECTIONS) {
                return mIncomingHeaderController;
            }
            return null;
            if (RankingCoordinator.SHOW_ALL_SECTIONS) mIncomingHeaderController else null
    }
    };

    private final OnHeadsUpChangedListener mOnHeadsUpChangedListener =
            new OnHeadsUpChangedListener() {
        @Override
        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
    private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener {
        override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
            if (!isHeadsUp) {
                mHeadsUpViewBinder.unbindHeadsUpView(entry);
                endNotifLifetimeExtensionIfExtended(entry);
                mHeadsUpViewBinder.unbindHeadsUpView(entry)
                endNotifLifetimeExtensionIfExtended(entry)
            }
        }
    };

    private boolean isSticky(NotificationEntry entry) {
        return mHeadsUpManager.isSticky(entry.getKey());
    }

    private boolean isCurrentlyShowingHun(ListEntry entry) {
        return mHeadsUpManager.isAlerting(entry.getKey());
    }
    private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)

    private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key)

    private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) {
    private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
        if (mNotifsExtendingLifetime.remove(entry)) {
            mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry);
            mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry)
        }
    }

    companion object {
        private const val TAG = "HeadsUpCoordinator"
    }
}
 No newline at end of file
+0 −274

File deleted.

Preview size limit exceeded, changes collapsed.

+238 −0

File added.

Preview size limit exceeded, changes collapsed.