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

Commit 126b8fc8 authored by Anton Potapov's avatar Anton Potapov
Browse files

Incorporate ANC slice into the Volume Panel button

Flag: aconfig new_volume_panel TEAMFOOD
Test: manual on the phone with a supported headset
Test: atest AncSliceInteractorTest
Fixes: 328369400
Change-Id: I5d4ef3067e94b32eea2cd28581a6bfdfb5f58ac5
parent dce24433
Loading
Loading
Loading
Loading
+5 −14
Original line number Original line Diff line number Diff line
@@ -17,15 +17,12 @@
package com.android.systemui.volume.panel.component.anc
package com.android.systemui.volume.panel.component.anc


import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
import com.android.systemui.volume.panel.component.anc.ui.composable.AncPopup
import com.android.systemui.volume.panel.component.anc.ui.composable.AncButtonComponent
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
import dagger.Binds
import dagger.Module
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
import dagger.multibindings.StringKey


@@ -40,14 +37,8 @@ interface AncModule {
        criteria: AncAvailabilityCriteria
        criteria: AncAvailabilityCriteria
    ): ComponentAvailabilityCriteria
    ): ComponentAvailabilityCriteria


    companion object {
    @Binds

        @Provides
    @IntoMap
    @IntoMap
    @StringKey(VolumePanelComponents.ANC)
    @StringKey(VolumePanelComponents.ANC)
        fun provideVolumePanelUiComponent(
    fun bindVolumePanelUiComponent(component: AncButtonComponent): VolumePanelUiComponent
            viewModel: AncViewModel,
            popup: AncPopup,
        ): VolumePanelUiComponent = ButtonComponent(viewModel.button, popup::show)
    }
}
}
+84 −0
Original line number Original line 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.panel.component.anc.ui.composable

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import javax.inject.Inject

class AncButtonComponent
@Inject
constructor(
    private val viewModel: AncViewModel,
    private val ancPopup: AncPopup,
) : ComposeVolumePanelUiComponent {

    @Composable
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
        val slice by viewModel.buttonSlice.collectAsState()
        val label = stringResource(R.string.volume_panel_noise_control_title)
        Column(
            modifier = modifier,
            verticalArrangement = Arrangement.spacedBy(12.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            SliceAndroidView(
                modifier =
                    Modifier.height(64.dp)
                        .fillMaxWidth()
                        .semantics {
                            role = Role.Button
                            contentDescription = label
                        }
                        .clip(RoundedCornerShape(28.dp)),
                slice = slice,
                onWidthChanged = viewModel::onButtonSliceWidthChanged,
                onClick = { ancPopup.show(null) }
            )
            Text(
                modifier = Modifier.clearAndSetSemantics {},
                text = label,
                style = MaterialTheme.typography.labelMedium,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}
+7 −43
Original line number Original line Diff line number Diff line
@@ -16,9 +16,6 @@


package com.android.systemui.volume.panel.component.anc.ui.composable
package com.android.systemui.volume.panel.component.anc.ui.composable


import android.content.Context
import android.view.ContextThemeWrapper
import android.view.View
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
@@ -30,9 +27,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.viewinterop.AndroidView
import androidx.slice.Slice
import androidx.slice.Slice
import androidx.slice.widget.SliceView
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -49,7 +44,7 @@ constructor(
) {
) {


    /** Shows a popup with the [expandable] animation. */
    /** Shows a popup with the [expandable] animation. */
    fun show(expandable: Expandable) {
    fun show(expandable: Expandable?) {
        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
    }
    }


@@ -66,49 +61,18 @@ constructor(


    @Composable
    @Composable
    private fun Content(dialog: SystemUIDialog) {
    private fun Content(dialog: SystemUIDialog) {
        val slice: Slice? by viewModel.slice.collectAsState()
        val isAvailable by viewModel.isAvailable.collectAsState(true)


        if (slice == null) {
        if (!isAvailable) {
            SideEffect { dialog.dismiss() }
            SideEffect { dialog.dismiss() }
            return
            return
        }
        }


        AndroidView<SliceView>(
        val slice by viewModel.popupSlice.collectAsState()
        SliceAndroidView(
            modifier = Modifier.fillMaxWidth(),
            modifier = Modifier.fillMaxWidth(),
            factory = { context: Context ->
            slice = slice,
                SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
            onWidthChanged = viewModel::onPopupSliceWidthChanged
                    .apply {
                        mode = SliceView.MODE_LARGE
                        isScrollable = false
                        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
                        setShowTitleItems(true)
                        addOnLayoutChangeListener(
                            OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
        )
        )
    }
    }
            },
            update = { sliceView: SliceView -> sliceView.slice = slice }
        )
    }

    private class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
        View.OnLayoutChangeListener {
        override fun onLayoutChange(
            v: View?,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
            oldLeft: Int,
            oldTop: Int,
            oldRight: Int,
            oldBottom: Int
        ) {
            val newWidth = right - left
            val oldWidth = oldRight - oldLeft
            if (oldWidth != newWidth) {
                widthChanged(newWidth)
            }
        }
    }
}
}
+107 −0
Original line number Original line 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.panel.component.anc.ui.composable

import android.annotation.SuppressLint
import android.content.Context
import android.view.ContextThemeWrapper
import android.view.MotionEvent
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.slice.Slice
import androidx.slice.widget.SliceView
import com.android.systemui.res.R

@Composable
fun SliceAndroidView(
    slice: Slice?,
    modifier: Modifier = Modifier,
    onWidthChanged: ((Int) -> Unit)? = null,
    onClick: (() -> Unit)? = null,
) {
    AndroidView(
        modifier = modifier,
        factory = { context: Context ->
            ClickableSliceView(
                    ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
                    onClick,
                )
                .apply {
                    mode = SliceView.MODE_LARGE
                    isScrollable = false
                    importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
                    setShowTitleItems(true)
                    if (onWidthChanged != null) {
                        addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
                    }
                    if (onClick != null) {
                        setOnClickListener { onClick() }
                    }
                }
        },
        update = { sliceView: SliceView -> sliceView.slice = slice }
    )
}

class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
    View.OnLayoutChangeListener {

    override fun onLayoutChange(
        v: View?,
        left: Int,
        top: Int,
        right: Int,
        bottom: Int,
        oldLeft: Int,
        oldTop: Int,
        oldRight: Int,
        oldBottom: Int
    ) {
        val newWidth = right - left
        val oldWidth = oldRight - oldLeft
        if (oldWidth != newWidth) {
            widthChanged(newWidth)
        }
    }
}

/**
 * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice
 * first.
 */
@SuppressLint("ViewConstructor") // only used in this class
private class ClickableSliceView(
    context: Context,
    private val onClick: (() -> Unit)?,
) : SliceView(context) {

    init {
        if (onClick != null) {
            setOnClickListener {}
        }
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        return onClick != null || super.onInterceptTouchEvent(ev)
    }

    override fun onClick(v: View?) {
        onClick?.let { it() } ?: super.onClick(v)
    }
}
+2 −2
Original line number Original line Diff line number Diff line
@@ -59,7 +59,7 @@ constructor(
     * @param content is the popup body
     * @param content is the popup body
     */
     */
    fun show(
    fun show(
        expandable: Expandable,
        expandable: Expandable?,
        title: @Composable (SystemUIDialog) -> Unit,
        title: @Composable (SystemUIDialog) -> Unit,
        content: @Composable (SystemUIDialog) -> Unit,
        content: @Composable (SystemUIDialog) -> Unit,
    ) {
    ) {
@@ -70,7 +70,7 @@ constructor(
            ) {
            ) {
                PopupComposable(it, title, content)
                PopupComposable(it, title, content)
            }
            }
        val controller = expandable.dialogTransitionController()
        val controller = expandable?.dialogTransitionController()
        if (controller == null) {
        if (controller == null) {
            dialog.show()
            dialog.show()
        } else {
        } else {
Loading