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

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

Make Volume Dialog fullscreen to support expanding animations

Flag: com.android.systemui.volume_redesign
Bug: 369994090
Test: manual on the foldable
Change-Id: If5749aead72f8e57dd7eabde2164d02f05ee0d5c
parent 48ba57a6
Loading
Loading
Loading
Loading
+47 −39
Original line number Diff line number Diff line
@@ -13,16 +13,20 @@
     See the License for the specific language governing permissions and
     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"
    android:id="@+id/volume_dialog_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/volume_dialog_container"
        android:layout_width="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:orientation="horizontal"
    android:showDividers="middle|end|beginning"
    android:theme="@style/volume_dialog_theme">
        android:showDividers="middle|end|beginning">

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

            <include layout="@layout/volume_ringer_drawer" />
@@ -60,3 +67,4 @@
                android:tint="?androidprv:attr/materialColorPrimary" />
        </LinearLayout>
    </LinearLayout>
</FrameLayout>
 No newline at end of file
+10 −0
Original line number Diff line number Diff line
@@ -557,6 +557,16 @@
        <item name="android:showWhenLocked">true</item>
    </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">
        <item name="trackHeight">40dp</item>
        <item name="thumbHeight">52dp</item>
+29 −1
Original line number Diff line number Diff line
@@ -18,9 +18,13 @@ package com.android.systemui.volume.dialog

import android.app.Dialog
import android.content.Context
import android.graphics.PixelFormat
import android.os.Bundle
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.WindowManager
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -32,10 +36,34 @@ constructor(
    @Application context: Context,
    private val viewBinder: VolumeDialogViewBinder,
    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?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.volume_dialog)
        viewBinder.bind(this)
    }

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

import android.app.Dialog
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
import android.graphics.Rect
import android.graphics.Region
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
import android.widget.FrameLayout
import androidx.annotation.GravityInt
import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.util.children
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.ringer.ui.binder.VolumeDialogRingerViewBinder
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.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

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

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

                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()
            }
        }
        volumeDialogRingerViewBinder.bind(view)
        slidersViewBinder.bind(view)
        settingsButtonViewBinder.bind(view)
    }

    /** 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)
        volumeDialogRingerViewBinder.bind(root)
        slidersViewBinder.bind(root)
        settingsButtonViewBinder.bind(root)
    }

    private fun CoroutineScope.animateVisibility(
@@ -209,4 +188,33 @@ constructor(
        }
        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 }
    }
}