Merge "Showing dialog even when dismissed externally and timeout didn't pass" into udc-dev am: 4538ab9b8f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23195697 Change-Id: Iab1f7a0d916b88f0bc43571b61a591ad17349937 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -21,41 +21,44 @@ import android.content.Context
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
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 javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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
|
||||
* indicator
|
||||
*/
|
||||
@SysUISingleton
|
||||
class KeyboardBacklightDialogCoordinator
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val context: Context,
|
||||
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
|
||||
|
||||
fun startListening() {
|
||||
applicationScope.launch {
|
||||
viewModel.dialogContent.collect { dialogViewModel ->
|
||||
if (dialogViewModel != null) {
|
||||
if (dialog == null) {
|
||||
dialog =
|
||||
KeyboardBacklightDialog(
|
||||
context,
|
||||
initialCurrentLevel = dialogViewModel.currentValue,
|
||||
initialMaxLevel = dialogViewModel.maxValue
|
||||
)
|
||||
dialog?.show()
|
||||
} else {
|
||||
dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
|
||||
}
|
||||
viewModel.dialogContent.collect { contentModel ->
|
||||
if (contentModel != null) {
|
||||
showDialog(contentModel)
|
||||
} else {
|
||||
dialog?.dismiss()
|
||||
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