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

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

Merge "Use the new Material sliders in the Volume Dialog" into main

parents e6ab634c a9b8c559
Loading
Loading
Loading
Loading
+14 −8
Original line number Diff line number Diff line
@@ -14,15 +14,21 @@
     limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/volume_dialog_slider_width"
    android:layout_height="@dimen/volume_dialog_slider_height">
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <com.google.android.material.slider.Slider
        style="@style/SystemUI.Material3.Slider.Volume"
        android:id="@+id/volume_dialog_slider"
        android:layout_width="@dimen/volume_dialog_slider_height"
        android:layout_height="match_parent"
        style="@style/SystemUI.Material3.Slider.Volume"
        android:layout_width="@dimen/volume_dialog_slider_width"
        android:layout_height="@dimen/volume_dialog_slider_height"
        android:layout_gravity="center"
        android:rotation="270"
        android:theme="@style/Theme.Material3.Light" />
        android:theme="@style/Theme.Material3.Light"
        android:orientation="vertical"
        app:thumbHeight="52dp"
        app:trackCornerSize="12dp"
        app:trackHeight="40dp"
        app:trackStopIndicatorSize="6dp"
        app:trackInsideCornerSize="2dp" />
</FrameLayout>
 No newline at end of file
+10 −6
Original line number Diff line number Diff line
@@ -18,11 +18,12 @@ package com.android.systemui.volume.dialog.sliders.ui

import android.animation.Animator
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.view.View
import android.view.animation.DecelerateInterpolator
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
@@ -48,24 +49,27 @@ constructor(
        val sliderView: Slider =
            view.requireViewById<Slider>(R.id.volume_dialog_slider).apply {
                labelBehavior = LabelFormatter.LABEL_GONE
                trackIconActiveColor = trackInactiveTintList
            }
        sliderView.addOnChangeListener { _, value, fromUser ->
            viewModel.setStreamVolume(value.roundToInt(), fromUser)
        }

        viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
        viewModel.state.onEach { it.bindToSlider(sliderView) }.launchIn(this)
    }

    private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
    @SuppressLint("UseCompatLoadingForDrawables")
    private suspend fun VolumeDialogSliderStateModel.bindToSlider(slider: Slider) {
        with(slider) {
            valueFrom = levelMin.toFloat()
            valueTo = levelMax.toFloat()
            valueFrom = minValue
            valueTo = maxValue
            // coerce the current value to the new value range before animating it
            value = value.coerceIn(valueFrom, valueTo)
            setValueAnimated(
                level.toFloat(),
                value,
                jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
            )
            trackIconActiveEnd = context.getDrawable(iconRes)
        }
    }
}
+122 −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.systemui.volume.dialog.sliders.ui.viewmodel

import android.media.AudioManager
import androidx.annotation.DrawableRes
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf

class VolumeDialogSliderIconProvider
@Inject
constructor(
    private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
    private val audioVolumeInteractor: AudioVolumeInteractor,
) {

    @DrawableRes
    fun getStreamIcon(
        stream: Int,
        level: Int,
        levelMin: Int,
        levelMax: Int,
        isMuted: Boolean,
        isRoutedToBluetooth: Boolean,
    ): Flow<Int> {
        return combine(
            notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)),
            ringerModeForStream(stream),
        ) { isZenMuted, ringerMode ->
            val isStreamOffline = level == 0 || isMuted
            if (isZenMuted) {
                // TODO(b/372466264) use icon for the corresponding zenmode
                return@combine com.android.internal.R.drawable.ic_qs_dnd
            }
            when (ringerMode?.value) {
                AudioManager.RINGER_MODE_VIBRATE ->
                    return@combine R.drawable.ic_volume_ringer_vibrate
                AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off
            }
            if (isRoutedToBluetooth) {
                return@combine if (stream == AudioManager.STREAM_VOICE_CALL) {
                    R.drawable.ic_volume_bt_sco
                } else {
                    if (isStreamOffline) {
                        R.drawable.ic_volume_media_bt_mute
                    } else {
                        R.drawable.ic_volume_media_bt
                    }
                }
            }

            return@combine if (isStreamOffline) {
                getMutedIconForStream(stream) ?: getIconForStream(stream)
            } else {
                if (level < (levelMax + levelMin) / 2) {
                    // This icon is different on TV
                    R.drawable.ic_volume_media_low
                } else {
                    getIconForStream(stream)
                }
            }
        }
    }

    @DrawableRes
    private fun getMutedIconForStream(stream: Int): Int? {
        return when (stream) {
            AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
            AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
            AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
            AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
            else -> null
        }
    }

    @DrawableRes
    private fun getIconForStream(stream: Int): Int {
        return when (stream) {
            AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility
            AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media
            AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
            AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
            AudioManager.STREAM_ALARM -> R.drawable.ic_alarm
            AudioManager.STREAM_VOICE_CALL -> com.android.internal.R.drawable.ic_phone
            AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system
            else -> error("Unsupported stream: $stream")
        }
    }

    /**
     * Emits [RingerMode] for the [stream] if it's affecting it and null when [RingerMode] doesn't
     * affect the [stream]
     */
    private fun ringerModeForStream(stream: Int): Flow<RingerMode?> {
        return if (stream == AudioManager.STREAM_RING) {
            audioVolumeInteractor.ringerMode
        } else {
            flowOf(null)
        }
    }
}
+36 −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.systemui.volume.dialog.sliders.ui.viewmodel

import androidx.annotation.DrawableRes
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel

data class VolumeDialogSliderStateModel(
    val minValue: Float,
    val maxValue: Float,
    val value: Float,
    @DrawableRes val iconRes: Int,
)

fun VolumeDialogStreamModel.toStateModel(@DrawableRes iconRes: Int): VolumeDialogSliderStateModel {
    return VolumeDialogSliderStateModel(
        minValue = levelMin.toFloat(),
        value = level.toFloat(),
        maxValue = levelMax.toFloat(),
        iconRes = iconRes,
    )
}
+19 −2
Original line number Diff line number Diff line
@@ -32,7 +32,9 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn

@@ -56,12 +58,12 @@ constructor(
    private val interactor: VolumeDialogSliderInteractor,
    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
    @VolumeDialog private val coroutineScope: CoroutineScope,
    private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
    private val systemClock: SystemClock,
) {

    private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)

    val model: Flow<VolumeDialogStreamModel> =
    private val model: Flow<VolumeDialogStreamModel> =
        interactor.slider
            .filter {
                val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
@@ -70,6 +72,21 @@ constructor(
            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
            .filterNotNull()

    val state: Flow<VolumeDialogSliderStateModel> =
        model.flatMapLatest { streamModel ->
            with(streamModel) {
                    volumeDialogSliderIconProvider.getStreamIcon(
                        stream = stream,
                        level = level,
                        levelMin = levelMin,
                        levelMax = levelMax,
                        isMuted = muted,
                        isRoutedToBluetooth = routedToBluetooth,
                    )
                }
                .map { icon -> streamModel.toStateModel(icon) }
        }

    init {
        userVolumeUpdates
            .filterNotNull()