summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt34
4 files changed, 91 insertions, 8 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index c0a873ac9a65..989b0de85e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -18,6 +18,7 @@ package com.android.systemui.display.ui.view
import android.content.Context
import android.os.Bundle
import android.view.View
+import android.view.WindowInsets
import android.widget.TextView
import androidx.core.view.updatePadding
import com.android.systemui.res.R
@@ -44,7 +45,10 @@ class MirroringConfirmationDialog(
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
private lateinit var dualDisplayWarning: TextView
+ private lateinit var bottomSheet: View
private var enabledPressed = false
+ private val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@ class MirroringConfirmationDialog(
visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
}
+ bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
setOnDismissListener {
if (!enabledPressed) {
onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@ class MirroringConfirmationDialog(
setupInsets()
}
- private fun setupInsets() {
+ private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
// This avoids overlap between dialog content and navigation bars.
- requireViewById<View>(R.id.cd_bottom_sheet).apply {
- val navbarInsets = navbarBottomInsetsProvider()
- val defaultDialogBottomInset =
- context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
- // we only care about the bottom inset as in all other configuration where navigations
- // are in other display sides there is no overlap with the dialog.
- updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ }
+
+ override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+ val navbarType = WindowInsets.Type.navigationBars()
+ if (changedTypes and navbarType != 0) {
+ setupInsets(insets.getInsets(navbarType).bottom)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 190062cdcca1..fbf0538e4bae 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -32,9 +32,12 @@ import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@ constructor(
private var dialog: Dialog? = null
/** Starts listening for pending displays. */
+ @OptIn(FlowPreview::class)
override fun start() {
val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@ constructor(
flow { emit(false) }
}
pendingDisplayFlow
+ // Let's debounce for 2 reasons:
+ // - prevent fast dialog flashes in case pending displays are available for just a few
+ // millis
+ // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+ // unlock triggers a change in insets that might result in a jump of the dialog (if a
+ // display was connected while on the lockscreen).
+ .debounce(200.milliseconds)
.combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
->
if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9556eb..541ac48a2feb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@ import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
import android.view.WindowManager.LayoutParams.MATCH_PARENT
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
@@ -70,16 +73,43 @@ open class SystemUIBottomSheetDialog(
override fun onStart() {
super.onStart()
configurationController?.addCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
}
override fun onStop() {
super.onStop()
configurationController?.removeCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(null)
}
+ /** Called after any insets change. */
+ open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
/** Can be overridden by subclasses to receive config changed events. */
open fun onConfigurationChanged() {}
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private var lastInsets: WindowInsets? = null
+
+ override fun onEnd(animation: WindowInsetsAnimation) {
+ lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+ }
+
+ override fun onProgress(
+ insets: WindowInsets,
+ animations: MutableList<WindowInsetsAnimation>,
+ ): WindowInsets {
+ lastInsets = insets
+ onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+ return insets
+ }
+
+ private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+ animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+ }
+
private val onConfigChanged =
object : ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e2757a..30519b0569d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.display.ui.view
+import android.graphics.Insets
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import android.view.WindowInsets
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -41,6 +44,7 @@ class MirroringConfirmationDialogTest : SysuiTestCase() {
private val onStartMirroringCallback = mock<View.OnClickListener>()
private val onCancelCallback = mock<View.OnClickListener>()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@ class MirroringConfirmationDialogTest : SysuiTestCase() {
verify(onStartMirroringCallback).onClick(any())
}
+ @Test
+ fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ @Test
+ fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isNotEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+ return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+ }
+
@After
fun teardown() {
if (::dialog.isInitialized) {
dialog.dismiss()
}
}
+
+ private companion object {
+ const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+ }
}