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

Commit ae92a469 authored by Anton Potapov's avatar Anton Potapov
Browse files

Add volume sliders domain layer

Flag: aconfig new_volume_panel DISABLED
Test: atest NotificationsSoundPolicyInteractorTest
Test: atest AudioVolumeInteractorTest
Test: atest VolumeSliderInteractorTest
Fixes: 318349141
Change-Id: I4dbaa52972fd4103b138f7954a250a2245804593
parent a85b4bea
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -40,3 +40,21 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor
        mutableZenMode.value = zenMode
    }
}

fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy(
    priorityCategories: Int = 0,
    priorityCallSenders: Int = NotificationManager.Policy.PRIORITY_SENDERS_ANY,
    priorityMessageSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
    suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
    state: Int = NotificationManager.Policy.STATE_UNSET,
    priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
) = updateNotificationPolicy(
    NotificationManager.Policy(
        priorityCategories,
        priorityCallSenders,
        priorityMessageSenders,
        suppressedVisualEffects,
        state,
        priorityConversationSenders,
    )
)
 No newline at end of file
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.statusbar.notification.domain.interactor

import android.app.NotificationManager
import android.media.AudioManager
import android.provider.Settings
import android.service.notification.ZenModeConfig
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
import com.android.settingslib.volume.shared.model.AudioStream
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map

/** Determines notification sounds state and limitations. */
class NotificationsSoundPolicyInteractor(
    private val repository: NotificationsSoundPolicyRepository
) {

    /** @see NotificationManager.getNotificationPolicy */
    val notificationPolicy: StateFlow<NotificationManager.Policy?>
        get() = repository.notificationPolicy

    /** @see NotificationManager.getZenMode */
    val zenMode: StateFlow<ZenMode?>
        get() = repository.zenMode

    /** Checks if [notificationPolicy] allows alarms. */
    val areAlarmsAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowAlarms() }

    /** Checks if [notificationPolicy] allows media. */
    val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }

    /** Checks if [notificationPolicy] allows ringer. */
    val isRingerAllowed: Flow<Boolean?> =
        notificationPolicy.map { policy ->
            policy ?: return@map null
            !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(policy)
        }

    /** Checks if the [stream] is muted by either [zenMode] or [notificationPolicy]. */
    fun isZenMuted(stream: AudioStream): Flow<Boolean> {
        return combine(
            zenMode.filterNotNull(),
            areAlarmsAllowed.filterNotNull(),
            isMediaAllowed.filterNotNull(),
            isRingerAllowed.filterNotNull(),
        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
            if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
                return@combine true
            }

            val isNotificationOrRing =
                stream.value == AudioManager.STREAM_RING ||
                    stream.value == AudioManager.STREAM_NOTIFICATION
            if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
                return@combine true
            }
            if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
                return@combine false
            }

            if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
                return@combine true
            }
            if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
                return@combine true
            }
            if (isNotificationOrRing && !isRingerAllowed) {
                return@combine true
            }

            return@combine false
        }
    }
}
+19 −14
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -64,10 +65,10 @@ interface AudioRepository {
    val communicationDevice: StateFlow<AudioDeviceInfo?>

    /** State of the [AudioStream]. */
    suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
    fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>

    /** Current state of the [AudioStream]. */
    suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel
    /** Returns the last audible volume before stream was muted. */
    suspend fun getLastAudibleVolume(audioStream: AudioStream): Int

    suspend fun setVolume(audioStream: AudioStream, volume: Int)

@@ -122,7 +123,7 @@ class AudioRepositoryImpl(
                    audioManager.communicationDevice,
                )

    override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
    override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
        return audioManagerEventsReceiver.events
            .filter {
                if (it is StreamAudioManagerEvent) {
@@ -132,21 +133,25 @@ class AudioRepositoryImpl(
                }
            }
            .map { getCurrentAudioStream(audioStream) }
            .onStart { emit(getCurrentAudioStream(audioStream)) }
            .flowOn(backgroundCoroutineContext)
    }

    override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
        return withContext(backgroundCoroutineContext) {
            AudioStreamModel(
    private fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
        return AudioStreamModel(
            audioStream = audioStream,
            minVolume = getMinVolume(audioStream),
            maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
            volume = audioManager.getStreamVolume(audioStream.value),
                isAffectedByRingerMode =
                    audioManager.isStreamAffectedByRingerMode(audioStream.value),
                isMuted = audioManager.isStreamMute(audioStream.value)
            isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
            isMuted = audioManager.isStreamMute(audioStream.value),
        )
    }

    override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int {
        return withContext(backgroundCoroutineContext) {
            audioManager.getLastAudibleStreamVolume(audioStream.value)
        }
    }

    override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.volume.domain.interactor

import android.media.AudioManager
import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

/** Provides audio stream state and an ability to change it */
class AudioVolumeInteractor(
    private val audioRepository: AudioRepository,
    private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
) {

    /** State of the [AudioStream]. */
    fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
        combine(
            audioRepository.getAudioStream(audioStream),
            audioRepository.ringerMode,
            notificationsSoundPolicyInteractor.isZenMuted(audioStream)
        ) { streamModel: AudioStreamModel, ringerMode: RingerMode, isZenMuted: Boolean ->
            streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
        }

    suspend fun setVolume(audioStream: AudioStream, volume: Int) =
        audioRepository.setVolume(audioStream, volume)

    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
        audioRepository.setMuted(audioStream, isMuted)

    /** Checks if the volume can be changed via the UI. */
    fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
        return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
            getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
        } else {
            flowOf(true)
        }
    }

    private suspend fun processVolume(
        audioStreamModel: AudioStreamModel,
        ringerMode: RingerMode,
        isZenMuted: Boolean,
    ): Int {
        if (isZenMuted) {
            return audioRepository.getLastAudibleVolume(audioStreamModel.audioStream)
        }
        val isNotificationOrRing =
            audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
                audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION
        if (isNotificationOrRing && ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
            // For ringer-mode affected streams, show volume as zero when ringer mode is vibrate
            if (
                audioStreamModel.audioStream.value == AudioManager.STREAM_RING ||
                    (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
                        audioStreamModel.isMuted)
            ) {
                return 0
            }
        } else if (audioStreamModel.isMuted) {
            return 0
        }
        return audioStreamModel.volume
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ class AudioManagerEventsReceiverImpl(
                AudioManager.VOLUME_CHANGED_ACTION,
                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
                AudioManager.ACTION_VOLUME_CHANGED,
            )

    override val events: SharedFlow<AudioManagerEvent> =
Loading