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

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

Merge "Make Volume Dialog fullscreen to support expanding animations" into main

parents df78d83f d762d7be
Loading
Loading
Loading
Loading
+47 −39
Original line number Original line Diff line number Diff line
@@ -13,16 +13,20 @@
     See the License for the specific language governing permissions and
     See the License for the specific language governing permissions and
     limitations under the License.
     limitations under the License.
-->
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
    android:id="@+id/volume_dialog_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/volume_dialog_container"
        android:id="@+id/volume_dialog_container"
        android:layout_width="wrap_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_height="wrap_content"
    android:layout_gravity="right"
        android:layout_gravity="center_vertical|end"
        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
        android:divider="@drawable/volume_dialog_floating_sliders_spacer"
        android:orientation="horizontal"
        android:orientation="horizontal"
    android:showDividers="middle|end|beginning"
        android:showDividers="middle|end|beginning">
    android:theme="@style/volume_dialog_theme">


        <LinearLayout
        <LinearLayout
            android:id="@+id/volume_dialog_floating_sliders_container"
            android:id="@+id/volume_dialog_floating_sliders_container"
@@ -39,10 +43,13 @@
            android:layout_width="@dimen/volume_dialog_width"
            android:layout_width="@dimen/volume_dialog_width"
            android:layout_height="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/volume_dialog_background"
            android:background="@drawable/volume_dialog_background"
            android:clipChildren="false"
            android:clipToOutline="false"
            android:clipToPadding="false"
            android:divider="@drawable/volume_dialog_spacer"
            android:divider="@drawable/volume_dialog_spacer"
        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
            android:gravity="center_horizontal"
            android:gravity="center_horizontal"
            android:orientation="vertical"
            android:orientation="vertical"
            android:paddingVertical="@dimen/volume_dialog_vertical_padding"
            android:showDividers="middle">
            android:showDividers="middle">


            <include layout="@layout/volume_ringer_drawer" />
            <include layout="@layout/volume_ringer_drawer" />
@@ -60,3 +67,4 @@
                android:tint="?androidprv:attr/materialColorPrimary" />
                android:tint="?androidprv:attr/materialColorPrimary" />
        </LinearLayout>
        </LinearLayout>
    </LinearLayout>
    </LinearLayout>
</FrameLayout>
 No newline at end of file
+10 −0
Original line number Original line Diff line number Diff line
@@ -557,6 +557,16 @@
        <item name="android:showWhenLocked">true</item>
        <item name="android:showWhenLocked">true</item>
    </style>
    </style>


    <style name="Theme.SystemUI.Dialog.Volume">
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:showWhenLocked">true</item>
        <item name="android:windowBackground">@color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:windowNoTitle">true</item>
    </style>

    <style name="SystemUI.Material3.Slider.Volume">
    <style name="SystemUI.Material3.Slider.Volume">
        <item name="trackHeight">40dp</item>
        <item name="trackHeight">40dp</item>
        <item name="thumbHeight">52dp</item>
        <item name="thumbHeight">52dp</item>
+29 −1
Original line number Original line Diff line number Diff line
@@ -18,9 +18,13 @@ package com.android.systemui.volume.dialog


import android.app.Dialog
import android.app.Dialog
import android.content.Context
import android.content.Context
import android.graphics.PixelFormat
import android.os.Bundle
import android.os.Bundle
import android.view.MotionEvent
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.WindowManager
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.volume.Events
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -32,10 +36,34 @@ constructor(
    @Application context: Context,
    @Application context: Context,
    private val viewBinder: VolumeDialogViewBinder,
    private val viewBinder: VolumeDialogViewBinder,
    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
) : Dialog(context) {
) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {

    init {
        with(window!!) {
            addFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
            )
            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)

            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
            setWindowAnimations(-1)
            setFormat(PixelFormat.TRANSLUCENT)

            attributes =
                attributes.apply {
                    title = "VolumeDialog" // Not the same as Window#setTitle
                }
            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        }
        setCanceledOnTouchOutside(true)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.volume_dialog)
        viewBinder.bind(this)
        viewBinder.bind(this)
    }
    }


+56 −48
Original line number Original line Diff line number Diff line
@@ -17,21 +17,22 @@
package com.android.systemui.volume.dialog.ui.binder
package com.android.systemui.volume.dialog.ui.binder


import android.app.Dialog
import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
import android.graphics.PixelFormat
import android.graphics.Region
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.Gravity
import android.view.View
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.Window
import android.view.ViewTreeObserver
import android.view.WindowManager
import android.view.ViewTreeObserver.InternalInsetsInfo
import android.widget.FrameLayout
import androidx.annotation.GravityInt
import com.android.internal.view.RotationPolicy
import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.util.children
import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
@@ -52,6 +53,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine


/** Binds the root view of the Volume Dialog. */
/** Binds the root view of the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -61,65 +64,41 @@ class VolumeDialogViewBinder
constructor(
constructor(
    private val volumeResources: VolumeDialogResources,
    private val volumeResources: VolumeDialogResources,
    private val gravityViewModel: VolumeDialogGravityViewModel,
    private val gravityViewModel: VolumeDialogGravityViewModel,
    private val viewModelFactory: VolumeDialogViewModel.Factory,
    private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
    private val jankListenerFactory: JankListenerFactory,
    private val jankListenerFactory: JankListenerFactory,
    private val tracer: VolumeTracer,
    private val tracer: VolumeTracer,
    @VolumeDialog private val coroutineScope: CoroutineScope,
    private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
    private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
    private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
    private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
) {
) {


    fun bind(dialog: Dialog) {
    fun bind(dialog: Dialog) {
        setupDialog(dialog)
        // Root view of the Volume Dialog.
        val view: View = dialog.requireViewById(R.id.volume_dialog_container)
        val root: ViewGroup = dialog.requireViewById(R.id.volume_dialog_root)
        view.alpha = 0f
        // Volume Dialog container view that contains the dialog itself without the floating sliders
        view.repeatWhenAttached {
        val container: View = root.requireViewById(R.id.volume_dialog_container)
            view.viewModel(
        container.alpha = 0f
        container.repeatWhenAttached {
            root.viewModel(
                traceName = "VolumeDialogViewBinder",
                traceName = "VolumeDialogViewBinder",
                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                factory = { viewModelFactory.create() },
                factory = { dialogViewModelFactory.create() },
            ) { viewModel ->
            ) { viewModel ->
                animateVisibility(container, dialog, viewModel.dialogVisibilityModel)

                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
                gravityViewModel.dialogGravity
                    .onEach { container.setLayoutGravity(it) }
                    .launchIn(this)


                animateVisibility(view, dialog, viewModel.dialogVisibilityModel)
                launch { root.viewTreeObserver.computeInternalInsetsListener(root) }


                awaitCancellation()
                awaitCancellation()
            }
            }
        }
        }
        volumeDialogRingerViewBinder.bind(view)
        volumeDialogRingerViewBinder.bind(root)
        slidersViewBinder.bind(view)
        slidersViewBinder.bind(root)
        settingsButtonViewBinder.bind(view)
        settingsButtonViewBinder.bind(root)
    }

    /** Configures [Window] for the [Dialog]. */
    private fun setupDialog(dialog: Dialog) {
        with(dialog.window!!) {
            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
            addFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
            )
            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)

            requestFeature(Window.FEATURE_NO_TITLE)
            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
            setWindowAnimations(-1)
            setFormat(PixelFormat.TRANSLUCENT)

            attributes =
                attributes.apply {
                    title = "VolumeDialog" // Not the same as Window#setTitle
                }
            setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)

            gravityViewModel.dialogGravity.onEach { setGravity(it) }.launchIn(coroutineScope)
        }
        dialog.setContentView(R.layout.volume_dialog)
        dialog.setCanceledOnTouchOutside(true)
    }
    }


    private fun CoroutineScope.animateVisibility(
    private fun CoroutineScope.animateVisibility(
@@ -209,4 +188,33 @@ constructor(
        }
        }
        animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
        animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
    }
    }

    private suspend fun ViewTreeObserver.computeInternalInsetsListener(viewGroup: ViewGroup) =
        suspendCancellableCoroutine<Unit> { continuation ->
            val listener =
                ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
                    viewGroup.fillTouchableBounds(inoutInfo)
                }
            addOnComputeInternalInsetsListener(listener)
            continuation.invokeOnCancellation { removeOnComputeInternalInsetsListener(listener) }
        }

    private fun ViewGroup.fillTouchableBounds(internalInsetsInfo: InternalInsetsInfo) {
        for (child in children) {
            val boundsRect = Rect()
            internalInsetsInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION)

            child.getBoundsInWindow(boundsRect, false)
            internalInsetsInfo.touchableRegion.op(boundsRect, Region.Op.UNION)
        }
        val boundsRect = Rect()
        getBoundsInWindow(boundsRect, false)
    }

    private fun View.setLayoutGravity(@GravityInt newGravity: Int) {
        val frameLayoutParams =
            layoutParams as? FrameLayout.LayoutParams
                ?: error("View must be a child of a FrameLayout")
        layoutParams = frameLayoutParams.apply { gravity = newGravity }
    }
}
}