Merge "Showing dialog even when dismissed externally and timeout didn't pass" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4538ab9b8f
@@ -21,41 +21,44 @@ import android.content.Context
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.keyboard.backlight.ui.view.KeyboardBacklightDialog
|
import com.android.systemui.keyboard.backlight.ui.view.KeyboardBacklightDialog
|
||||||
|
import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
|
||||||
import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogViewModel
|
import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private fun defaultCreateDialog(context: Context): (Int, Int) -> KeyboardBacklightDialog {
|
||||||
|
return { currentLevel: Int, maxLevel: Int ->
|
||||||
|
KeyboardBacklightDialog(context, currentLevel, maxLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on the state produced from [BacklightDialogViewModel] shows or hides keyboard backlight
|
* Based on the state produced from [BacklightDialogViewModel] shows or hides keyboard backlight
|
||||||
* indicator
|
* indicator
|
||||||
*/
|
*/
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class KeyboardBacklightDialogCoordinator
|
class KeyboardBacklightDialogCoordinator
|
||||||
@Inject
|
|
||||||
constructor(
|
constructor(
|
||||||
@Application private val applicationScope: CoroutineScope,
|
@Application private val applicationScope: CoroutineScope,
|
||||||
private val context: Context,
|
|
||||||
private val viewModel: BacklightDialogViewModel,
|
private val viewModel: BacklightDialogViewModel,
|
||||||
|
private val createDialog: (Int, Int) -> KeyboardBacklightDialog
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@Application applicationScope: CoroutineScope,
|
||||||
|
context: Context,
|
||||||
|
viewModel: BacklightDialogViewModel
|
||||||
|
) : this(applicationScope, viewModel, defaultCreateDialog(context))
|
||||||
|
|
||||||
var dialog: KeyboardBacklightDialog? = null
|
var dialog: KeyboardBacklightDialog? = null
|
||||||
|
|
||||||
fun startListening() {
|
fun startListening() {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
viewModel.dialogContent.collect { dialogViewModel ->
|
viewModel.dialogContent.collect { contentModel ->
|
||||||
if (dialogViewModel != null) {
|
if (contentModel != null) {
|
||||||
if (dialog == null) {
|
showDialog(contentModel)
|
||||||
dialog =
|
|
||||||
KeyboardBacklightDialog(
|
|
||||||
context,
|
|
||||||
initialCurrentLevel = dialogViewModel.currentValue,
|
|
||||||
initialMaxLevel = dialogViewModel.maxValue
|
|
||||||
)
|
|
||||||
dialog?.show()
|
|
||||||
} else {
|
|
||||||
dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
dialog = null
|
dialog = null
|
||||||
@@ -63,4 +66,15 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showDialog(model: BacklightDialogContentViewModel) {
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = createDialog(model.currentValue, model.maxValue)
|
||||||
|
} else {
|
||||||
|
dialog?.updateState(model.currentValue, model.maxValue)
|
||||||
|
}
|
||||||
|
// let's always show dialog - even if we're just updating it, it might have been dismissed
|
||||||
|
// externally by tapping finger outside of it
|
||||||
|
dialog?.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.keyboard.backlight.ui
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor
|
||||||
|
import com.android.systemui.keyboard.backlight.ui.view.KeyboardBacklightDialog
|
||||||
|
import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogViewModel
|
||||||
|
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
|
||||||
|
import com.android.systemui.keyboard.shared.model.BacklightModel
|
||||||
|
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.times
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class KeyboardBacklightDialogCoordinatorTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
|
||||||
|
@Mock private lateinit var dialog: KeyboardBacklightDialog
|
||||||
|
|
||||||
|
private val keyboardRepository = FakeKeyboardRepository()
|
||||||
|
private lateinit var underTest: KeyboardBacklightDialogCoordinator
|
||||||
|
private val timeoutMillis = 3000L
|
||||||
|
private val testScope = TestScope(StandardTestDispatcher())
|
||||||
|
|
||||||
|
private val createDialog = { value: Int, maxValue: Int ->
|
||||||
|
dialogCreationValue = value
|
||||||
|
dialogCreationMaxValue = maxValue
|
||||||
|
dialog
|
||||||
|
}
|
||||||
|
private var dialogCreationValue = -1
|
||||||
|
private var dialogCreationMaxValue = -1
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(accessibilityManagerWrapper.getRecommendedTimeoutMillis(any(), any()))
|
||||||
|
.thenReturn(timeoutMillis.toInt())
|
||||||
|
val viewModel =
|
||||||
|
BacklightDialogViewModel(
|
||||||
|
KeyboardBacklightInteractor(keyboardRepository),
|
||||||
|
accessibilityManagerWrapper
|
||||||
|
)
|
||||||
|
underTest =
|
||||||
|
KeyboardBacklightDialogCoordinator(testScope.backgroundScope, viewModel, createDialog)
|
||||||
|
underTest.startListening()
|
||||||
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showsDialog_afterBacklightChange() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(1)
|
||||||
|
|
||||||
|
verify(dialog).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updatesDialog_withLatestValues_afterBacklightChange() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(value = 1, maxValue = 5)
|
||||||
|
setBacklightValue(value = 2, maxValue = 5)
|
||||||
|
|
||||||
|
verify(dialog).updateState(2, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showsDialog_withDataFromBacklightChange() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(value = 4, maxValue = 5)
|
||||||
|
|
||||||
|
Truth.assertThat(dialogCreationValue).isEqualTo(4)
|
||||||
|
Truth.assertThat(dialogCreationMaxValue).isEqualTo(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dismissesDialog_afterTimeout() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(1)
|
||||||
|
|
||||||
|
advanceTimeBy(timeoutMillis + 1)
|
||||||
|
|
||||||
|
verify(dialog).dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dismissesDialog_onlyAfterTimeout_fromLastBacklightChange() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(1)
|
||||||
|
advanceTimeBy(timeoutMillis * 2 / 3)
|
||||||
|
// majority of timeout passed
|
||||||
|
|
||||||
|
// this should restart timeout
|
||||||
|
setBacklightValue(2)
|
||||||
|
advanceTimeBy(timeoutMillis * 2 / 3)
|
||||||
|
verify(dialog, never()).dismiss()
|
||||||
|
|
||||||
|
advanceTimeBy(timeoutMillis * 2 / 3)
|
||||||
|
// finally timeout reached and dialog was dismissed
|
||||||
|
verify(dialog, times(1)).dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showsDialog_ifItWasAlreadyShownAndDismissedBySomethingElse() =
|
||||||
|
testScope.runTest {
|
||||||
|
setBacklightValue(1)
|
||||||
|
// let's pretend dialog is dismissed e.g. by user tapping on the screen
|
||||||
|
whenever(dialog.isShowing).thenReturn(false)
|
||||||
|
|
||||||
|
// no advancing time, we're still in timeout period
|
||||||
|
setBacklightValue(2)
|
||||||
|
|
||||||
|
verify(dialog, times(2)).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TestScope.setBacklightValue(value: Int, maxValue: Int = MAX_BACKLIGHT) {
|
||||||
|
keyboardRepository.setBacklight(BacklightModel(value, maxValue))
|
||||||
|
runCurrent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_BACKLIGHT = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 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.keyboard.backlight.ui.viewmodel
|
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest
|
|
||||||
import com.android.systemui.SysuiTestCase
|
|
||||||
import com.android.systemui.coroutines.collectLastValue
|
|
||||||
import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor
|
|
||||||
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
|
|
||||||
import com.android.systemui.keyboard.shared.model.BacklightModel
|
|
||||||
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
|
|
||||||
import com.android.systemui.util.mockito.any
|
|
||||||
import com.android.systemui.util.mockito.whenever
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.test.advanceTimeBy
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.JUnit4
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@SmallTest
|
|
||||||
@RunWith(JUnit4::class)
|
|
||||||
class BacklightDialogViewModelTest : SysuiTestCase() {
|
|
||||||
|
|
||||||
private val keyboardRepository = FakeKeyboardRepository()
|
|
||||||
private lateinit var underTest: BacklightDialogViewModel
|
|
||||||
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
|
|
||||||
private val timeoutMillis = 3000L
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this)
|
|
||||||
whenever(accessibilityManagerWrapper.getRecommendedTimeoutMillis(any(), any()))
|
|
||||||
.thenReturn(timeoutMillis.toInt())
|
|
||||||
underTest =
|
|
||||||
BacklightDialogViewModel(
|
|
||||||
KeyboardBacklightInteractor(keyboardRepository),
|
|
||||||
accessibilityManagerWrapper
|
|
||||||
)
|
|
||||||
keyboardRepository.setIsAnyKeyboardConnected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun emitsViewModel_whenBacklightChanged() = runTest {
|
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
|
||||||
|
|
||||||
assertThat(underTest.dialogContent.first()).isEqualTo(BacklightDialogContentViewModel(1, 5))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun emitsNull_afterTimeout() = runTest {
|
|
||||||
val latest by collectLastValue(underTest.dialogContent)
|
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
|
||||||
|
|
||||||
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
|
|
||||||
advanceTimeBy(timeoutMillis + 1)
|
|
||||||
assertThat(latest).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun emitsNull_after5secDelay_fromLastBacklightChange() = runTest {
|
|
||||||
val latest by collectLastValue(underTest.dialogContent)
|
|
||||||
keyboardRepository.setIsAnyKeyboardConnected(true)
|
|
||||||
|
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
|
||||||
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
|
|
||||||
|
|
||||||
advanceTimeBy(timeoutMillis * 2 / 3)
|
|
||||||
// timeout yet to pass, no new emission
|
|
||||||
keyboardRepository.setBacklight(BacklightModel(2, 5))
|
|
||||||
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(2, 5))
|
|
||||||
|
|
||||||
advanceTimeBy(timeoutMillis * 2 / 3)
|
|
||||||
// timeout refreshed because of last `setBacklight`, still content present
|
|
||||||
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(2, 5))
|
|
||||||
|
|
||||||
advanceTimeBy(timeoutMillis * 2 / 3)
|
|
||||||
// finally timeout reached and null emitted
|
|
||||||
assertThat(latest).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user