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

Commit e7c3efc7 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Add volume sliders domain layer" into main

parents a46e7255 ae92a469
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