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

Commit bd5bb894 authored by Julia Tuttle's avatar Julia Tuttle
Browse files

Start implementing visual interruption suppressors

Bug: 261728888
Test: atest NotificationInterruptStateProviderWrapperTest
Test: atest VisualInterruptionDecisionProviderImplTest
Flag: ACONFIG com.android.systemui.visual_interruptions_refactor DEVELOPMENT
Change-Id: I253cd540bb8d65bd764fe4e524f4dfdf995b9ecb
parent b69e3386
Loading
Loading
Loading
Loading
+90 −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 android.database.ContentObserver
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.settings.GlobalSettings

class PeekDisabledSuppressor(
    private val globalSettings: GlobalSettings,
    private val headsUpManager: HeadsUpManager,
    private val logger: NotificationInterruptLogger,
    @Main private val mainHandler: Handler,
) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
    private var isEnabled = false

    override fun shouldSuppress(): Boolean = !isEnabled

    override fun start() {
        val observer =
            object : ContentObserver(mainHandler) {
                override fun onChange(selfChange: Boolean) {
                    val wasEnabled = isEnabled

                    isEnabled =
                        globalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF) !=
                            HEADS_UP_OFF

                    // QQQ: Do we want to log this even if it hasn't changed?
                    logger.logHeadsUpFeatureChanged(isEnabled)

                    // QQQ: Is there a better place for this side effect? What if HeadsUpManager
                    // registered for it directly?
                    if (wasEnabled && !isEnabled) {
                        logger.logWillDismissAll()
                        headsUpManager.releaseAllImmediately()
                    }
                }
            }

        globalSettings.registerContentObserver(
            globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
            /* notifyForDescendants = */ true,
            observer
        )

        // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.

        observer.onChange(/* selfChange = */ true)
    }
}

class PulseDisabledSuppressor(
    private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
    private val userTracker: UserTracker,
) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
    override fun shouldSuppress(): Boolean =
        !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
}

class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
    VisualInterruptionCondition(
        types = setOf(PULSE),
        reason = "pulsing disabled by battery saver"
    ) {
    override fun shouldSuppress() = batteryController.isAodPowerSave()
}
+7 −0
Original line number Diff line number Diff line
@@ -51,6 +51,13 @@ interface VisualInterruptionDecisionProvider {
        val wouldInterruptWithoutDnd: Boolean
    }

    /**
     * Initializes the provider.
     *
     * Must be called before any method except [addLegacySuppressor].
     */
    fun start() {}

    /**
     * Adds a [component][suppressor] that can suppress visual interruptions.
     *
+19 −0
Original line number Diff line number Diff line
@@ -48,6 +48,18 @@ constructor(
    private val systemClock: SystemClock,
    private val userTracker: UserTracker,
) : VisualInterruptionDecisionProvider {
    private var started = false

    override fun start() {
        check(!started)

        addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler))
        addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
        addCondition(PulseBatterySaverSuppressor(batteryController))

        started = true
    }

    private class DecisionImpl(
        override val shouldInterrupt: Boolean,
        override val logReason: String
@@ -76,27 +88,33 @@ constructor(

    fun addCondition(condition: VisualInterruptionCondition) {
        conditions.add(condition)
        condition.start()
    }

    fun addFilter(filter: VisualInterruptionFilter) {
        filters.add(filter)
        filter.start()
    }

    override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
        check(started)
        return makeHeadsUpDecision(entry)
    }

    override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
        check(started)
        return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
    }

    override fun makeUnloggedFullScreenIntentDecision(
        entry: NotificationEntry
    ): FullScreenIntentDecision {
        check(started)
        return makeFullScreenDecision(entry)
    }

    override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
        check(started)
        val decisionImpl =
            decision as? FullScreenIntentDecisionImpl
                ?: run {
@@ -112,6 +130,7 @@ constructor(
    }

    override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
        check(started)
        return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
    }

+6 −0
Original line number Diff line number Diff line
@@ -51,6 +51,12 @@ sealed interface VisualInterruptionSuppressor {

    /** An optional UiEvent ID to be recorded when this suppresses an interruption. */
    val uiEventId: UiEventEnum?

    /**
     * Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
     * any other methods are called on the suppressor.
     */
    fun start() {}
}

/** A reason why visual interruptions might be suppressed regardless of the notification. */
+16 −17
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision
                systemClock,
                globalSettings,
            )
                .also { it.mUseHeadsUp = true }
        )
    }

Loading