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

Commit 3d8fd3f5 authored by Julia Tuttle's avatar Julia Tuttle Committed by Automerger Merge Worker
Browse files

Merge changes from topic "interruptions-refactor-0" into udc-dev am: fb67b8af

parents 80a845bb fb67b8af
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -85,6 +85,8 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -116,16 +118,16 @@ import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;

import java.util.Optional;
import java.util.concurrent.Executor;

import javax.inject.Named;

import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;

import java.util.Optional;
import java.util.concurrent.Executor;

import javax.inject.Named;

/**
 * A dagger module for injecting components of System UI that are required by System UI.
 *
@@ -315,4 +317,11 @@ public abstract class SystemUIModule {
    @Binds
    abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
            LargeScreenShadeInterpolatorImpl impl);

    @SysUISingleton
    @Provides
    static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
            NotificationInterruptStateProvider innerProvider) {
        return new NotificationInterruptStateProviderWrapper(innerProvider);
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision

/**
 * Wraps a [NotificationInterruptStateProvider] to convert it to the new
 * [VisualInterruptionDecisionProvider] interface.
 */
@SysUISingleton
class NotificationInterruptStateProviderWrapper(
    private val wrapped: NotificationInterruptStateProvider
) : VisualInterruptionDecisionProvider {

    @VisibleForTesting
    enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision {
        SHOULD_INTERRUPT(shouldInterrupt = true),
        SHOULD_NOT_INTERRUPT(shouldInterrupt = false);

        companion object {
            fun of(booleanDecision: Boolean) =
                if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
        }
    }

    @VisibleForTesting
    class FullScreenIntentDecisionImpl(
        val originalEntry: NotificationEntry,
        val originalDecision: NotificationInterruptStateProvider.FullScreenIntentDecision
    ) : FullScreenIntentDecision {
        override val shouldInterrupt = originalDecision.shouldLaunch
        override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
    }

    override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {
        wrapped.addSuppressor(suppressor)
    }

    override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
        wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }

    override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision =
        wrapped.checkHeadsUp(entry, /* log= */ true).let { DecisionImpl.of(it) }

    override fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry) =
        wrapped.getFullScreenIntentDecision(entry).let { FullScreenIntentDecisionImpl(entry, it) }

    override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
        (decision as FullScreenIntentDecisionImpl).let {
            wrapped.logFullScreenIntentDecision(it.originalEntry, it.originalDecision)
        }
    }

    override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
        wrapped.shouldBubbleUp(entry).let { DecisionImpl.of(it) }
}
+112 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.statusbar.notification.collection.NotificationEntry

/**
 * Decides whether a notification should visually interrupt the user in various ways.
 *
 * These include displaying the notification as heads-up (peeking while the device is awake or
 * pulsing while the device is dozing), displaying the notification as a bubble, and launching a
 * full-screen intent for the notification.
 */
interface VisualInterruptionDecisionProvider {
    /**
     * Represents the decision to visually interrupt or not.
     *
     * Used for heads-up and bubble decisions; subclassed by [FullScreenIntentDecision] for
     * full-screen intent decisions.
     *
     * @property[shouldInterrupt] whether a visual interruption should be triggered
     */
    interface Decision {
        val shouldInterrupt: Boolean
    }

    /**
     * Represents the decision to launch a full-screen intent for a notification or not.
     *
     * @property[wouldInterruptWithoutDnd] whether a full-screen intent should not be launched only
     *   because Do Not Disturb has suppressed it
     */
    interface FullScreenIntentDecision : Decision {
        val wouldInterruptWithoutDnd: Boolean
    }

    /**
     * Adds a [component][suppressor] that can suppress visual interruptions.
     *
     * This class may call suppressors in any order.
     *
     * @param[suppressor] the suppressor to add
     */
    fun addSuppressor(suppressor: NotificationInterruptSuppressor)

    /**
     * Decides whether a [notification][entry] should display as heads-up or not, but does not log
     * that decision.
     *
     * @param[entry] the notification that this decision is about
     * @return the decision to display that notification as heads-up or not
     */
    fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision

    /**
     * Decides whether a [notification][entry] should display as heads-up or not, and logs that
     * decision.
     *
     * If the device is awake, the decision will consider whether the notification should "peek"
     * (slide in from the top of the screen over the current activity).
     *
     * If the device is dozing, the decision will consider whether the notification should "pulse"
     * (wake the screen up and display the ambient view of the notification).
     *
     * @see[makeUnloggedHeadsUpDecision]
     *
     * @param[entry] the notification that this decision is about
     * @return the decision to display that notification as heads-up or not
     */
    fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision

    /**
     * Decides whether a [notification][entry] should launch a full-screen intent or not, but does
     * not log that decision.
     *
     * The returned decision can be logged by passing it to [logFullScreenIntentDecision].
     *
     * @see[makeAndLogHeadsUpDecision]
     *
     * @param[entry] the notification that this decision is about
     * @return the decision to launch a full-screen intent for that notification or not
     */
    fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision

    /**
     * Logs a previous [decision] to launch a full-screen intent or not.
     *
     * @param[decision] the decision to log
     */
    fun logFullScreenIntentDecision(decision: FullScreenIntentDecision)

    /**
     * Decides whether a [notification][entry] should display as a bubble or not.
     *
     * @param[entry] the notification that this decision is about
     * @return the decision to display that notification as a bubble or not
     */
    fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision
}
+78 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar.notification.interruption

import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {

    @Test
    fun decisionOfTrue() {
        assertTrue(DecisionImpl.of(true).shouldInterrupt)
    }

    @Test
    fun decisionOfFalse() {
        assertFalse(DecisionImpl.of(false).shouldInterrupt)
    }

    @Test
    fun decisionOfTrueInterned() {
        assertEquals(DecisionImpl.of(true), DecisionImpl.of(true))
    }

    @Test
    fun decisionOfFalseInterned() {
        assertEquals(DecisionImpl.of(false), DecisionImpl.of(false))
    }

    @Test
    fun fullScreenIntentDecisionShouldInterrupt() {
        makeFsiDecision(FSI_DEVICE_NOT_INTERACTIVE).let {
            assertTrue(it.shouldInterrupt)
            assertFalse(it.wouldInterruptWithoutDnd)
        }
    }

    @Test
    fun fullScreenIntentDecisionShouldNotInterrupt() {
        makeFsiDecision(NO_FSI_NOT_IMPORTANT_ENOUGH).let {
            assertFalse(it.shouldInterrupt)
            assertFalse(it.wouldInterruptWithoutDnd)
        }
    }

    @Test
    fun fullScreenIntentDecisionWouldInterruptWithoutDnd() {
        makeFsiDecision(NO_FSI_SUPPRESSED_ONLY_BY_DND).let {
            assertFalse(it.shouldInterrupt)
            assertTrue(it.wouldInterruptWithoutDnd)
        }
    }

    @Test
    fun fullScreenIntentDecisionWouldNotInterruptEvenWithoutDnd() {
        makeFsiDecision(NO_FSI_SUPPRESSED_BY_DND).let {
            assertFalse(it.shouldInterrupt)
            assertFalse(it.wouldInterruptWithoutDnd)
        }
    }

    private fun makeFsiDecision(originalDecision: FullScreenIntentDecision) =
        FullScreenIntentDecisionImpl(NotificationEntryBuilder().build(), originalDecision)
}