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

Commit 92c9c66f authored by Yuri Lin's avatar Yuri Lin
Browse files

Move full screen intent decisions to HeadsUpCoordinator

The actual handling to launch the full screen intent remains in StatusBarNotificationActivityStarter.

This adds a provider class because StatusBarNotificationActivityStarter and HeadsUpCoordinator are in different scopes. The provider class receives the decision to launch a full screen intent and passes it along to listeners for that event (in practice just StatusBarNotificationActivityStarter).

Bug: 248325248
Test: HeadsUpCoordinatorTest, StatusBarNotificationActivityStarterTest, manually verified that at least a regular full screen intent works
Change-Id: I2e8e81bc617b2819cbdf82fcc460d5ec931a572a
parent 46b33d3f
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
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.provider.LaunchFullScreenIntentProvider
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
@@ -68,6 +69,7 @@ class HeadsUpCoordinator @Inject constructor(
    private val mHeadsUpViewBinder: HeadsUpViewBinder,
    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
    private val mRemoteInputManager: NotificationRemoteInputManager,
    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
    @IncomingHeader private val mIncomingHeaderController: NodeController,
    @Main private val mExecutor: DelayableExecutor,
) : Coordinator {
@@ -373,6 +375,12 @@ class HeadsUpCoordinator @Inject constructor(
         * Notification was just added and if it should heads up, bind the view and then show it.
         */
        override fun onEntryAdded(entry: NotificationEntry) {
            // First check whether this notification should launch a full screen intent, and
            // launch it if needed.
            if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
                mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
            }

            // shouldHeadsUp includes check for whether this notification should be filtered
            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
            mPostedEntries[entry.key] = PostedEntry(
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.collection.provider

import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.util.ListenerSet
import javax.inject.Inject

/**
 * A class that enables communication of decisions to launch a notification's full screen intent.
 */
@SysUISingleton
class LaunchFullScreenIntentProvider @Inject constructor() {
    companion object {
        private const val TAG = "LaunchFullScreenIntentProvider"
    }
    private val listeners = ListenerSet<Listener>()

    /**
     * Registers a listener with this provider. These listeners will be alerted whenever a full
     * screen intent should be launched for a notification entry.
     */
    fun registerListener(listener: Listener) {
        listeners.addIfAbsent(listener)
    }

    /** Removes the specified listener. */
    fun removeListener(listener: Listener) {
        listeners.remove(listener)
    }

    /**
     * Sends a request to launch full screen intent for the given notification entry to all
     * registered listeners.
     */
    fun launchFullScreenIntent(entry: NotificationEntry) {
        if (listeners.isEmpty()) {
            // This should never happen, but we should definitely know if it does because having
            // no listeners would indicate that FSIs are getting entirely dropped on the floor.
            Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested")
        }
        for (listener in listeners) {
            listener.onFullScreenIntentRequested(entry)
        }
    }

    /** Listener interface for passing full screen intent launch decisions. */
    fun interface Listener {
        /**
         * Invoked whenever a full screen intent launch is requested for the given notification
         * entry.
         */
        fun onFullScreenIntentRequested(entry: NotificationEntry)
    }
}
+32 −49
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOp
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
@@ -60,9 +59,8 @@ import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -126,7 +124,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            Context context,
            Handler mainThreadHandler,
            Executor uiBgExecutor,
            NotifPipeline notifPipeline,
            NotificationVisibilityProvider visibilityProvider,
            HeadsUpManagerPhone headsUpManager,
            ActivityStarter activityStarter,
@@ -151,7 +148,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            NotificationPresenter presenter,
            NotificationPanelViewController panel,
            ActivityLaunchAnimator activityLaunchAnimator,
            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
            LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
        mContext = context;
        mMainThreadHandler = mainThreadHandler;
        mUiBgExecutor = uiBgExecutor;
@@ -182,12 +180,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
        mActivityLaunchAnimator = activityLaunchAnimator;
        mNotificationAnimationProvider = notificationAnimationProvider;

        notifPipeline.addCollectionListener(new NotifCollectionListener() {
            @Override
            public void onEntryAdded(NotificationEntry entry) {
                handleFullScreenIntent(entry);
            }
        });
        launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
    }

    /**
@@ -549,13 +542,13 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
    }

    @VisibleForTesting
    void handleFullScreenIntent(NotificationEntry entry) {
        if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
            if (shouldSuppressFullScreenIntent(entry)) {
                mLogger.logFullScreenIntentSuppressedByDnD(entry);
            } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
                mLogger.logFullScreenIntentNotImportantEnough(entry);
            } else {
    void launchFullScreenIntent(NotificationEntry entry) {
        // Skip if device is in VR mode.
        if (mPresenter.isDeviceInVrMode()) {
            mLogger.logFullScreenIntentSuppressedByVR(entry);
            return;
        }

        // Stop screensaver if the notification has a fullscreen intent.
        // (like an incoming phone call)
        mUiBgExecutor.execute(() -> {
@@ -581,8 +574,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            // ignore
        }
    }
        }
    }

    @Override
    public boolean isCollapsingToShowActivityOverLockscreen() {
@@ -607,12 +598,4 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            mMainThreadHandler.post(mShadeController::collapsePanel);
        }
    }

    private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
        if (mPresenter.isDeviceInVrMode()) {
            return true;
        }

        return entry.shouldSuppressFullScreenIntent();
    }
}
+2 −10
Original line number Diff line number Diff line
@@ -96,19 +96,11 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor(
        })
    }

    fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) {
    fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) {
        buffer.log(TAG, DEBUG, {
            str1 = entry.logKey
        }, {
            "No Fullscreen intent: suppressed by DND: $str1"
        })
    }

    fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) {
        buffer.log(TAG, DEBUG, {
            str1 = entry.logKey
        }, {
            "No Fullscreen intent: not important enough: $str1"
            "No Fullscreen intent: suppressed by VR mode: $str1"
        })
    }

+22 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
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.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
@@ -86,6 +87,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
    private val mRemoteInputManager: NotificationRemoteInputManager = mock()
    private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
    private val mHeaderController: NodeController = mock()
    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()

    private lateinit var mEntry: NotificationEntry
    private lateinit var mGroupSummary: NotificationEntry
@@ -110,6 +112,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
            mHeadsUpViewBinder,
            mNotificationInterruptStateProvider,
            mRemoteInputManager,
            mLaunchFullScreenIntentProvider,
            mHeaderController,
            mExecutor)
        mCoordinator.attach(mNotifPipeline)
@@ -241,6 +244,20 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
        verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
    }

    @Test
    fun testOnEntryAdded_shouldFullScreen() {
        setShouldFullScreen(mEntry)
        mCollectionListener.onEntryAdded(mEntry)
        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
    }

    @Test
    fun testOnEntryAdded_shouldNotFullScreen() {
        setShouldFullScreen(mEntry, should = false)
        mCollectionListener.onEntryAdded(mEntry)
        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
    }

    @Test
    fun testPromotesAddedHUN() {
        // GIVEN the current entry should heads up
@@ -755,6 +772,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
                .thenReturn(should)
    }

    private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
        whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
            .thenReturn(should)
    }

    private fun finishBind(entry: NotificationEntry) {
        verify(mHeadsUpManager, never()).showNotification(entry)
        withArgCaptor<BindCallback> {
Loading