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

Commit 3fda276e authored by Behnam Heydarshahi's avatar Behnam Heydarshahi Committed by Android (Google) Code Review
Browse files

Merge "Updatable flashlight slider default level" into main

parents 7176579c 0f788152
Loading
Loading
Loading
Loading
+130 −16
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -365,7 +365,7 @@ class FlashlightRepositoryTest : SysuiTestCase() {
        }

    @Test
    fun enableSetLevelDisable_stateUpdatesReturnsToDisabledAndDefaultLevel() =
    fun enableSetLevelDisable_stateUpdatesSetsToDisabledAndLastLevel() =
        kosmos.runTest {
            injectCameraCharacteristics(true, CameraCharacteristics.LENS_FACING_BACK)

@@ -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)
                )
        }

@@ -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
@@ -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 {
@@ -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),
            )
    }
}
+38 −1
Original line number Diff line number Diff line
@@ -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)
@@ -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 {
+66 −15
Original line number Diff line number Diff line
@@ -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
@@ -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>
@@ -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
@@ -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 {
@@ -109,6 +118,8 @@ constructor(

    private var _deviceSupportsFlashlight = false

    private val defaultEnabledLevelForUser = ConcurrentHashMap<Int, Int>()

    override fun start() {
        if (FlashlightStrength.isUnexpectedlyInLegacyMode()) {
            return
@@ -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
                }
@@ -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))
            }

@@ -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
                                    )
                                )
@@ -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."
                            )
                        }
                }
@@ -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."
                            )
                        }
                }
@@ -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
@@ -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}")
@@ -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")
@@ -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}")
@@ -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
+4 −0
Original line number Diff line number Diff line
@@ -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
+2 −1
Original line number Diff line number Diff line
@@ -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