Loading packages/SystemUI/multivalentTests/src/com/android/systemui/flashlight/data/repository/FlashlightRepositoryTest.kt +130 −16 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.flashlight.data.repository import android.content.packageManager import android.content.pm.PackageManager import android.content.pm.UserInfo import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager.TorchCallback import android.platform.test.annotations.EnableFlags Loading @@ -32,16 +33,15 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import org.junit.Assert import org.junit.Assert.assertThrows import java.util.concurrent.Executor import kotlin.time.Duration.Companion.seconds import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito Loading Loading @@ -365,7 +365,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { } @Test fun enableSetLevelDisable_stateUpdatesReturnsToDisabledAndDefaultLevel() = fun enableSetLevelDisable_stateUpdatesSetsToDisabledAndLastLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) Loading Loading @@ -393,7 +393,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { underTest.setEnabled(false) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) FlashlightModel.Available.Level(false, DEFAULT_MAX_LEVEL, DEFAULT_MAX_LEVEL) ) } Loading Loading @@ -444,7 +444,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { runCurrent() underTest.setEnabled(true) verify(cameraManager, times(1)).setTorchMode(anyString(), anyBoolean()) verify(cameraManager, times(1)).turnOnTorchWithStrengthLevel(anyString(), anyInt()) } @Test Loading Loading @@ -473,6 +473,74 @@ class FlashlightRepositoryTest : SysuiTestCase() { verify(cameraManager, times(1)).turnOnTorchWithStrengthLevel(anyString(), anyInt()) } @Test fun setLevelTemporaryDisableEnable_forgetsTemporaryLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val tempLevel = 2 val permLevel = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) underTest.setTemporaryLevel(tempLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, tempLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(false) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(false, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(true) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) } @Test fun setLevelTemporaryDisableEnable_remembersLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val permLevel = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(false) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(false, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(true) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) } @Test fun initiallyFailLoadingThenFlashlightBecomesAvailable_afterCooldown_userEnables_connectsAndEnables() = kosmos.runTest { Loading Loading @@ -563,11 +631,57 @@ class FlashlightRepositoryTest : SysuiTestCase() { assertThat(underTest.deviceSupportsFlashlight).isTrue() } @Test fun setLevelDisableSwitchUser_secondUserHasItsOwnDefaultLevel() = kosmos.runTest { fakeUserRepository.setUserInfos(USER_INFOS) fakeUserRepository.setSelectedUserInfo(USER_INFOS[0]) injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val permLevelForUser1 = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevelForUser1) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(true, permLevelForUser1, DEFAULT_MAX_LEVEL) ) underTest.setEnabled(false) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, permLevelForUser1, DEFAULT_MAX_LEVEL) ) fakeUserRepository.setSelectedUserInfo(USER_INFOS[1]) underTest.setEnabled(true) assertThat(state) .isNotEqualTo( FlashlightModel.Available.Level(true, permLevelForUser1, DEFAULT_MAX_LEVEL) ) } companion object { private const val BASE_TORCH_LEVEL = 1 private const val DEFAULT_DEFAULT_LEVEL = 21 private const val DEFAULT_MAX_LEVEL = 45 private const val DEFAULT_ID = "ID" private val RECONNECT_COOLDOWN = 30.seconds private val USER_INFOS = listOf( UserInfo(/* id= */ 100, /* name= */ "First user", /* flags= */ 0), UserInfo(/* id= */ 101, /* name= */ "Second user", /* flags= */ 0), ) } } packages/SystemUI/multivalentTests/src/com/android/systemui/flashlight/ui/viewmodel/FlashlightSliderViewModelTest.kt +38 −1 Original line number Diff line number Diff line Loading @@ -190,7 +190,7 @@ class FlashlightSliderViewModelTest : SysuiTestCase() { runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(false) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(DEFAULT_LEVEL) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(2) // can set level at max flashlightInteractor.setLevel(MAX_LEVEL) Loading @@ -200,6 +200,43 @@ class FlashlightSliderViewModelTest : SysuiTestCase() { assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(MAX_LEVEL) } @Test fun updateInteractor_temporaryUpdatesAreForgottenAndPermanentOnesRemembered() = kosmos.runTest { flashlightInteractor.setEnabled(true) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(DEFAULT_LEVEL) assertThat(underTest.currentFlashlightLevel!!.max).isEqualTo(MAX_LEVEL) underTest.setFlashlightLevel(1) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) underTest.setFlashlightLevelTemporary(2) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(2) // instead it can disable the flashlight flashlightInteractor.setEnabled(false) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(false) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) // can set level at max flashlightInteractor.setEnabled(true) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) } @Test fun flashlightIsAdjustable_turnsTrueAfterInitialization() = kosmos.runTest { Loading packages/SystemUI/src/com/android/systemui/flashlight/data/repository/FlashlightRepository.kt +66 −15 Original line number Diff line number Diff line Loading @@ -30,11 +30,13 @@ import com.android.systemui.flashlight.flags.FlashlightStrength import com.android.systemui.flashlight.shared.logger.FlashlightLogger import com.android.systemui.flashlight.shared.model.FlashlightModel import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.asIndenting import com.android.systemui.util.printSection import com.android.systemui.util.println import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.time.Duration.Companion.seconds Loading @@ -58,7 +60,7 @@ import kotlinx.coroutines.withTimeoutOrNull /** * Provides information about flashlight availability, its state of being on/off, and level. Can be * used to enable/disable or set level of flashlight * used to enable/disable or set level of flashlight. */ interface FlashlightRepository { val state: StateFlow<FlashlightModel> Loading @@ -69,6 +71,12 @@ interface FlashlightRepository { /** Consistent with [CameraManager.turnOnTorchWithStrengthLevel] level cannot be 0 */ fun setLevel(level: Int) /** * The level will not be remembered when the flashlight is disabled and enabled. This function * is useful onValueChange and should be finished with a [setLevel] call onValueChangeFinished. */ fun setTemporaryLevel(level: Int) } @SysUISingleton Loading @@ -82,6 +90,7 @@ constructor( private val dumpManager: DumpManager, private val packageManager: PackageManager, private val logger: FlashlightLogger, private val userRepo: UserRepository, ) : FlashlightRepository, CoreStartable { private sealed interface FlashlightInfo { Loading Loading @@ -109,6 +118,8 @@ constructor( private var _deviceSupportsFlashlight = false private val defaultEnabledLevelForUser = ConcurrentHashMap<Int, Int>() override fun start() { if (FlashlightStrength.isUnexpectedlyInLegacyMode()) { return Loading Loading @@ -159,8 +170,8 @@ constructor( foundCamera } else { logger.d( "Need to wait for ${RECONNECT_COOLDOWN.inWholeSeconds}" + " seconds from last attempt before trying to reconnect." "Need to wait for ${RECONNECT_COOLDOWN.inWholeSeconds} seconds from" + " last attempt before trying to reconnect." ) false } Loading Loading @@ -197,6 +208,9 @@ constructor( if (backFlashlightAvailable) { val default: Int? = cc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL) val max: Int? = cc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) if (default != null) { defaultEnabledLevelForUser[currentUserId] = default } flashlightInfo.emit(FlashlightInfo.Supported.LoadedSuccessfully(id, default, max)) } Loading Loading @@ -229,7 +243,13 @@ constructor( trySend( FlashlightModel.Available.Level( enabled, cameraManager.getTorchStrengthLevel(camId), if (enabled) { cameraManager.getTorchStrengthLevel(camId) } else { defaultEnabledLevelForUser.getOrPut(currentUserId) { initialDefaultLevel } }, currentFlashlightInfo.maxLevel!!, // b/c hasAdjustableLevels ) ) Loading @@ -238,9 +258,9 @@ constructor( } } else { logger.w( "onTorchModeChanged: saved camera id " + "was ${currentFlashlightInfo.cameraId}" + "but flashlight with camera id $camId called back." "onTorchModeChanged: saved camera id was" + " ${currentFlashlightInfo.cameraId} but flashlight with" + " camera id $camId called back." ) } } Loading @@ -263,16 +283,14 @@ constructor( ) else logger.w( "onTorchStrengthLevelChanged: " + "One of the levels was null or max was below base level. " + "default:${currentFlashlightInfo.defaultLevel}," + "max:${currentFlashlightInfo.maxLevel}" "onTorchStrengthLevelChanged: One of the levels was" + " null or max was below base level. default:${currentFlashlightInfo.defaultLevel}, max:${currentFlashlightInfo.maxLevel}" ) } else { logger.w( "onTorchStrengthLevelChanged: saved camera id " + "was ${currentFlashlightInfo.cameraId}" + "but flashlight with camera id $camId called back." "onTorchStrengthLevelChanged: saved camera id was" + " ${currentFlashlightInfo.cameraId} but flashlight with" + " camera id $camId called back." ) } } Loading @@ -282,6 +300,11 @@ constructor( awaitClose { cameraManager.unregisterTorchCallback(callbackFromSystem) } } /** * The only place this repo diverges from the [CameraManager.getTorchStrengthLevel] API, when * the flashlight is off. This API will show the last enabled level, but that one will show the * device default. */ @OptIn(ExperimentalCoroutinesApi::class) override val state: StateFlow<FlashlightModel> = flashlightInfo Loading Loading @@ -339,8 +362,17 @@ constructor( try { if (enabled != currentState.enabled) { if (currentFlashlightInfo.hasAdjustableLevels && enabled) { cameraManager.turnOnTorchWithStrengthLevel( currentFlashlightInfo.cameraId, defaultEnabledLevelForUser.getOrPut(currentUserId) { initialDefaultLevel }, ) } else { cameraManager.setTorchMode(currentFlashlightInfo.cameraId, enabled) } } } catch (e: CameraAccessException) { e.printStackTrace() logger.w("Error trying to setEnabled to $enabled: ${e.message}") Loading @@ -350,6 +382,14 @@ constructor( /** @throws IllegalArgumentException if level is below 1 and above max */ override fun setLevel(level: Int) { setLevel(level, true) } override fun setTemporaryLevel(level: Int) { setLevel(level, false) } private fun setLevel(level: Int, persist: Boolean) { bgScope.launch { if (!connectToCameraLoadFlashlightInfo()) { logger.w("Could not connect to a flashlight") Loading @@ -366,6 +406,9 @@ constructor( if (level != currentState.level) { cameraManager.turnOnTorchWithStrengthLevel(currentInfo.cameraId, level) } if (persist) { defaultEnabledLevelForUser[currentUserId] = level } } catch (e: CameraAccessException) { e.printStackTrace() logger.w("Error trying to setLevel to $level: ${e.message}") Loading @@ -387,6 +430,14 @@ constructor( } } private val initialDefaultLevel: Int get() = (flashlightInfo.value as? FlashlightInfo.Supported.LoadedSuccessfully)?.defaultLevel ?: BASE_TORCH_LEVEL private val currentUserId: Int get() = userRepo.selectedUser.value.userInfo.id private companion object { private const val BASE_TORCH_LEVEL = 1 private val RECONNECT_COOLDOWN = 30.seconds Loading packages/SystemUI/src/com/android/systemui/flashlight/domain/interactor/FlashlightInteractor.kt +4 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,10 @@ class FlashlightInteractor @Inject constructor(private val repository: Flashligh repository.setLevel(level) } fun setTemporaryLevel(level: Int) { repository.setTemporaryLevel(level) } val state = repository.state val deviceSupportsFlashlight: Boolean Loading packages/SystemUI/src/com/android/systemui/flashlight/ui/composable/FlashlightSlider.kt +2 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,8 @@ fun FlashlightSliderContainer(viewModel: FlashlightSliderViewModel, modifier: Mo VerticalFlashlightSlider( levelValue = levelValue, valueRange = 0..currentState.max, onValueChange = viewModel::setFlashlightLevel, onValueChange = viewModel::setFlashlightLevelTemporary, onValueChangeFinished = viewModel::setFlashlightLevel, isEnabled = viewModel.isFlashlightAdjustable, hapticsViewModelFactory = viewModel.hapticsViewModelFactory, colors = Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/flashlight/data/repository/FlashlightRepositoryTest.kt +130 −16 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.flashlight.data.repository import android.content.packageManager import android.content.pm.PackageManager import android.content.pm.UserInfo import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager.TorchCallback import android.platform.test.annotations.EnableFlags Loading @@ -32,16 +33,15 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import org.junit.Assert import org.junit.Assert.assertThrows import java.util.concurrent.Executor import kotlin.time.Duration.Companion.seconds import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito Loading Loading @@ -365,7 +365,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { } @Test fun enableSetLevelDisable_stateUpdatesReturnsToDisabledAndDefaultLevel() = fun enableSetLevelDisable_stateUpdatesSetsToDisabledAndLastLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) Loading Loading @@ -393,7 +393,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { underTest.setEnabled(false) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) FlashlightModel.Available.Level(false, DEFAULT_MAX_LEVEL, DEFAULT_MAX_LEVEL) ) } Loading Loading @@ -444,7 +444,7 @@ class FlashlightRepositoryTest : SysuiTestCase() { runCurrent() underTest.setEnabled(true) verify(cameraManager, times(1)).setTorchMode(anyString(), anyBoolean()) verify(cameraManager, times(1)).turnOnTorchWithStrengthLevel(anyString(), anyInt()) } @Test Loading Loading @@ -473,6 +473,74 @@ class FlashlightRepositoryTest : SysuiTestCase() { verify(cameraManager, times(1)).turnOnTorchWithStrengthLevel(anyString(), anyInt()) } @Test fun setLevelTemporaryDisableEnable_forgetsTemporaryLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val tempLevel = 2 val permLevel = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) underTest.setTemporaryLevel(tempLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, tempLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(false) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(false, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(true) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) } @Test fun setLevelTemporaryDisableEnable_remembersLevel() = kosmos.runTest { injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val permLevel = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevel) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(false) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(false, permLevel, DEFAULT_MAX_LEVEL)) underTest.setEnabled(true) assertThat(state) .isEqualTo(FlashlightModel.Available.Level(true, permLevel, DEFAULT_MAX_LEVEL)) } @Test fun initiallyFailLoadingThenFlashlightBecomesAvailable_afterCooldown_userEnables_connectsAndEnables() = kosmos.runTest { Loading Loading @@ -563,11 +631,57 @@ class FlashlightRepositoryTest : SysuiTestCase() { assertThat(underTest.deviceSupportsFlashlight).isTrue() } @Test fun setLevelDisableSwitchUser_secondUserHasItsOwnDefaultLevel() = kosmos.runTest { fakeUserRepository.setUserInfos(USER_INFOS) fakeUserRepository.setSelectedUserInfo(USER_INFOS[0]) injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK) val state by collectLastValue(underTest.state) val permLevelForUser1 = 3 startFlashlightRepository(true) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, DEFAULT_DEFAULT_LEVEL, DEFAULT_MAX_LEVEL) ) underTest.setLevel(permLevelForUser1) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(true, permLevelForUser1, DEFAULT_MAX_LEVEL) ) underTest.setEnabled(false) assertThat(state) .isEqualTo( FlashlightModel.Available.Level(false, permLevelForUser1, DEFAULT_MAX_LEVEL) ) fakeUserRepository.setSelectedUserInfo(USER_INFOS[1]) underTest.setEnabled(true) assertThat(state) .isNotEqualTo( FlashlightModel.Available.Level(true, permLevelForUser1, DEFAULT_MAX_LEVEL) ) } companion object { private const val BASE_TORCH_LEVEL = 1 private const val DEFAULT_DEFAULT_LEVEL = 21 private const val DEFAULT_MAX_LEVEL = 45 private const val DEFAULT_ID = "ID" private val RECONNECT_COOLDOWN = 30.seconds private val USER_INFOS = listOf( UserInfo(/* id= */ 100, /* name= */ "First user", /* flags= */ 0), UserInfo(/* id= */ 101, /* name= */ "Second user", /* flags= */ 0), ) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/flashlight/ui/viewmodel/FlashlightSliderViewModelTest.kt +38 −1 Original line number Diff line number Diff line Loading @@ -190,7 +190,7 @@ class FlashlightSliderViewModelTest : SysuiTestCase() { runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(false) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(DEFAULT_LEVEL) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(2) // can set level at max flashlightInteractor.setLevel(MAX_LEVEL) Loading @@ -200,6 +200,43 @@ class FlashlightSliderViewModelTest : SysuiTestCase() { assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(MAX_LEVEL) } @Test fun updateInteractor_temporaryUpdatesAreForgottenAndPermanentOnesRemembered() = kosmos.runTest { flashlightInteractor.setEnabled(true) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(DEFAULT_LEVEL) assertThat(underTest.currentFlashlightLevel!!.max).isEqualTo(MAX_LEVEL) underTest.setFlashlightLevel(1) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) underTest.setFlashlightLevelTemporary(2) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(2) // instead it can disable the flashlight flashlightInteractor.setEnabled(false) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(false) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) // can set level at max flashlightInteractor.setEnabled(true) runCurrent() assertThat(underTest.currentFlashlightLevel!!.enabled).isEqualTo(true) assertThat(underTest.currentFlashlightLevel!!.level).isEqualTo(1) } @Test fun flashlightIsAdjustable_turnsTrueAfterInitialization() = kosmos.runTest { Loading
packages/SystemUI/src/com/android/systemui/flashlight/data/repository/FlashlightRepository.kt +66 −15 Original line number Diff line number Diff line Loading @@ -30,11 +30,13 @@ import com.android.systemui.flashlight.flags.FlashlightStrength import com.android.systemui.flashlight.shared.logger.FlashlightLogger import com.android.systemui.flashlight.shared.model.FlashlightModel import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.asIndenting import com.android.systemui.util.printSection import com.android.systemui.util.println import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.time.Duration.Companion.seconds Loading @@ -58,7 +60,7 @@ import kotlinx.coroutines.withTimeoutOrNull /** * Provides information about flashlight availability, its state of being on/off, and level. Can be * used to enable/disable or set level of flashlight * used to enable/disable or set level of flashlight. */ interface FlashlightRepository { val state: StateFlow<FlashlightModel> Loading @@ -69,6 +71,12 @@ interface FlashlightRepository { /** Consistent with [CameraManager.turnOnTorchWithStrengthLevel] level cannot be 0 */ fun setLevel(level: Int) /** * The level will not be remembered when the flashlight is disabled and enabled. This function * is useful onValueChange and should be finished with a [setLevel] call onValueChangeFinished. */ fun setTemporaryLevel(level: Int) } @SysUISingleton Loading @@ -82,6 +90,7 @@ constructor( private val dumpManager: DumpManager, private val packageManager: PackageManager, private val logger: FlashlightLogger, private val userRepo: UserRepository, ) : FlashlightRepository, CoreStartable { private sealed interface FlashlightInfo { Loading Loading @@ -109,6 +118,8 @@ constructor( private var _deviceSupportsFlashlight = false private val defaultEnabledLevelForUser = ConcurrentHashMap<Int, Int>() override fun start() { if (FlashlightStrength.isUnexpectedlyInLegacyMode()) { return Loading Loading @@ -159,8 +170,8 @@ constructor( foundCamera } else { logger.d( "Need to wait for ${RECONNECT_COOLDOWN.inWholeSeconds}" + " seconds from last attempt before trying to reconnect." "Need to wait for ${RECONNECT_COOLDOWN.inWholeSeconds} seconds from" + " last attempt before trying to reconnect." ) false } Loading Loading @@ -197,6 +208,9 @@ constructor( if (backFlashlightAvailable) { val default: Int? = cc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL) val max: Int? = cc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) if (default != null) { defaultEnabledLevelForUser[currentUserId] = default } flashlightInfo.emit(FlashlightInfo.Supported.LoadedSuccessfully(id, default, max)) } Loading Loading @@ -229,7 +243,13 @@ constructor( trySend( FlashlightModel.Available.Level( enabled, cameraManager.getTorchStrengthLevel(camId), if (enabled) { cameraManager.getTorchStrengthLevel(camId) } else { defaultEnabledLevelForUser.getOrPut(currentUserId) { initialDefaultLevel } }, currentFlashlightInfo.maxLevel!!, // b/c hasAdjustableLevels ) ) Loading @@ -238,9 +258,9 @@ constructor( } } else { logger.w( "onTorchModeChanged: saved camera id " + "was ${currentFlashlightInfo.cameraId}" + "but flashlight with camera id $camId called back." "onTorchModeChanged: saved camera id was" + " ${currentFlashlightInfo.cameraId} but flashlight with" + " camera id $camId called back." ) } } Loading @@ -263,16 +283,14 @@ constructor( ) else logger.w( "onTorchStrengthLevelChanged: " + "One of the levels was null or max was below base level. " + "default:${currentFlashlightInfo.defaultLevel}," + "max:${currentFlashlightInfo.maxLevel}" "onTorchStrengthLevelChanged: One of the levels was" + " null or max was below base level. default:${currentFlashlightInfo.defaultLevel}, max:${currentFlashlightInfo.maxLevel}" ) } else { logger.w( "onTorchStrengthLevelChanged: saved camera id " + "was ${currentFlashlightInfo.cameraId}" + "but flashlight with camera id $camId called back." "onTorchStrengthLevelChanged: saved camera id was" + " ${currentFlashlightInfo.cameraId} but flashlight with" + " camera id $camId called back." ) } } Loading @@ -282,6 +300,11 @@ constructor( awaitClose { cameraManager.unregisterTorchCallback(callbackFromSystem) } } /** * The only place this repo diverges from the [CameraManager.getTorchStrengthLevel] API, when * the flashlight is off. This API will show the last enabled level, but that one will show the * device default. */ @OptIn(ExperimentalCoroutinesApi::class) override val state: StateFlow<FlashlightModel> = flashlightInfo Loading Loading @@ -339,8 +362,17 @@ constructor( try { if (enabled != currentState.enabled) { if (currentFlashlightInfo.hasAdjustableLevels && enabled) { cameraManager.turnOnTorchWithStrengthLevel( currentFlashlightInfo.cameraId, defaultEnabledLevelForUser.getOrPut(currentUserId) { initialDefaultLevel }, ) } else { cameraManager.setTorchMode(currentFlashlightInfo.cameraId, enabled) } } } catch (e: CameraAccessException) { e.printStackTrace() logger.w("Error trying to setEnabled to $enabled: ${e.message}") Loading @@ -350,6 +382,14 @@ constructor( /** @throws IllegalArgumentException if level is below 1 and above max */ override fun setLevel(level: Int) { setLevel(level, true) } override fun setTemporaryLevel(level: Int) { setLevel(level, false) } private fun setLevel(level: Int, persist: Boolean) { bgScope.launch { if (!connectToCameraLoadFlashlightInfo()) { logger.w("Could not connect to a flashlight") Loading @@ -366,6 +406,9 @@ constructor( if (level != currentState.level) { cameraManager.turnOnTorchWithStrengthLevel(currentInfo.cameraId, level) } if (persist) { defaultEnabledLevelForUser[currentUserId] = level } } catch (e: CameraAccessException) { e.printStackTrace() logger.w("Error trying to setLevel to $level: ${e.message}") Loading @@ -387,6 +430,14 @@ constructor( } } private val initialDefaultLevel: Int get() = (flashlightInfo.value as? FlashlightInfo.Supported.LoadedSuccessfully)?.defaultLevel ?: BASE_TORCH_LEVEL private val currentUserId: Int get() = userRepo.selectedUser.value.userInfo.id private companion object { private const val BASE_TORCH_LEVEL = 1 private val RECONNECT_COOLDOWN = 30.seconds Loading
packages/SystemUI/src/com/android/systemui/flashlight/domain/interactor/FlashlightInteractor.kt +4 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,10 @@ class FlashlightInteractor @Inject constructor(private val repository: Flashligh repository.setLevel(level) } fun setTemporaryLevel(level: Int) { repository.setTemporaryLevel(level) } val state = repository.state val deviceSupportsFlashlight: Boolean Loading
packages/SystemUI/src/com/android/systemui/flashlight/ui/composable/FlashlightSlider.kt +2 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,8 @@ fun FlashlightSliderContainer(viewModel: FlashlightSliderViewModel, modifier: Mo VerticalFlashlightSlider( levelValue = levelValue, valueRange = 0..currentState.max, onValueChange = viewModel::setFlashlightLevel, onValueChange = viewModel::setFlashlightLevelTemporary, onValueChangeFinished = viewModel::setFlashlightLevel, isEnabled = viewModel.isFlashlightAdjustable, hapticsViewModelFactory = viewModel.hapticsViewModelFactory, colors = Loading