Merge "Support animatable icons to quick affordances." into tm-qpr-dev
This commit is contained in:
@@ -21,50 +21,52 @@ import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||
import com.android.systemui.statusbar.policy.FlashlightController
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
@SysUISingleton
|
||||
class FlashlightQuickAffordanceConfig @Inject constructor(
|
||||
@Application private val context: Context,
|
||||
private val flashlightController: FlashlightController,
|
||||
class FlashlightQuickAffordanceConfig
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val context: Context,
|
||||
private val flashlightController: FlashlightController,
|
||||
) : KeyguardQuickAffordanceConfig {
|
||||
|
||||
private sealed class FlashlightState {
|
||||
|
||||
abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
|
||||
|
||||
object On: FlashlightState() {
|
||||
object On : FlashlightState() {
|
||||
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
Icon.Resource(
|
||||
R.drawable.ic_flashlight_on,
|
||||
R.drawable.qs_flashlight_icon_on,
|
||||
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
||||
),
|
||||
ActivationState.Active
|
||||
)
|
||||
}
|
||||
|
||||
object OffAvailable: FlashlightState() {
|
||||
object OffAvailable : FlashlightState() {
|
||||
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
Icon.Resource(
|
||||
R.drawable.ic_flashlight_off,
|
||||
R.drawable.qs_flashlight_icon_off,
|
||||
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
||||
),
|
||||
ActivationState.Inactive
|
||||
)
|
||||
}
|
||||
|
||||
object Unavailable: FlashlightState() {
|
||||
object Unavailable : FlashlightState() {
|
||||
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
|
||||
}
|
||||
@@ -77,57 +79,57 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
||||
get() = context.getString(R.string.quick_settings_flashlight_label)
|
||||
|
||||
override val pickerIconResourceId: Int
|
||||
get() = if (flashlightController.isEnabled) {
|
||||
R.drawable.ic_flashlight_on
|
||||
} else {
|
||||
R.drawable.ic_flashlight_off
|
||||
}
|
||||
get() = R.drawable.ic_flashlight_off
|
||||
|
||||
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
|
||||
conflatedCallbackFlow {
|
||||
val flashlightCallback = object : FlashlightController.FlashlightListener {
|
||||
override fun onFlashlightChanged(enabled: Boolean) {
|
||||
trySendWithFailureLogging(
|
||||
if (enabled) {
|
||||
FlashlightState.On.toLockScreenState()
|
||||
} else {
|
||||
FlashlightState.OffAvailable.toLockScreenState()
|
||||
},
|
||||
TAG
|
||||
)
|
||||
}
|
||||
conflatedCallbackFlow {
|
||||
val flashlightCallback =
|
||||
object : FlashlightController.FlashlightListener {
|
||||
override fun onFlashlightChanged(enabled: Boolean) {
|
||||
trySendWithFailureLogging(
|
||||
if (enabled) {
|
||||
FlashlightState.On.toLockScreenState()
|
||||
} else {
|
||||
FlashlightState.OffAvailable.toLockScreenState()
|
||||
},
|
||||
TAG
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFlashlightError() {
|
||||
trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
|
||||
}
|
||||
override fun onFlashlightError() {
|
||||
trySendWithFailureLogging(
|
||||
FlashlightState.OffAvailable.toLockScreenState(),
|
||||
TAG
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFlashlightAvailabilityChanged(available: Boolean) {
|
||||
trySendWithFailureLogging(
|
||||
if (!available) {
|
||||
FlashlightState.Unavailable.toLockScreenState()
|
||||
} else {
|
||||
if (flashlightController.isEnabled) {
|
||||
FlashlightState.On.toLockScreenState()
|
||||
} else {
|
||||
FlashlightState.OffAvailable.toLockScreenState()
|
||||
}
|
||||
},
|
||||
TAG
|
||||
)
|
||||
}
|
||||
override fun onFlashlightAvailabilityChanged(available: Boolean) {
|
||||
trySendWithFailureLogging(
|
||||
if (!available) {
|
||||
FlashlightState.Unavailable.toLockScreenState()
|
||||
} else {
|
||||
if (flashlightController.isEnabled) {
|
||||
FlashlightState.On.toLockScreenState()
|
||||
} else {
|
||||
FlashlightState.OffAvailable.toLockScreenState()
|
||||
}
|
||||
},
|
||||
TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
flashlightController.addCallback(flashlightCallback)
|
||||
|
||||
awaitClose { flashlightController.removeCallback(flashlightCallback) }
|
||||
}
|
||||
|
||||
flashlightController.addCallback(flashlightCallback)
|
||||
|
||||
awaitClose {
|
||||
flashlightController.removeCallback(flashlightCallback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTriggered(expandable: Expandable?):
|
||||
KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||
flashlightController
|
||||
.setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
|
||||
override fun onTriggered(
|
||||
expandable: Expandable?
|
||||
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||
flashlightController.setFlashlight(
|
||||
flashlightController.isAvailable && !flashlightController.isEnabled
|
||||
)
|
||||
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
|
||||
}
|
||||
|
||||
@@ -141,4 +143,4 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
||||
companion object {
|
||||
private const val TAG = "FlashlightQuickAffordanceConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.keyguard.ui.binder
|
||||
|
||||
import android.graphics.drawable.Animatable2
|
||||
import android.util.Size
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
@@ -27,12 +28,11 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.LockIconViewController
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
|
||||
@@ -73,7 +73,8 @@ object KeyguardBottomAreaViewBinder {
|
||||
fun onConfigurationChanged()
|
||||
|
||||
/**
|
||||
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
|
||||
* Returns whether the keyguard bottom area should be constrained to the top of the lock
|
||||
* icon
|
||||
*/
|
||||
fun shouldConstrainToTopOfLockIcon(): Boolean
|
||||
}
|
||||
@@ -217,7 +218,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
}
|
||||
|
||||
override fun shouldConstrainToTopOfLockIcon(): Boolean =
|
||||
viewModel.shouldConstrainToTopOfLockIcon()
|
||||
viewModel.shouldConstrainToTopOfLockIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +249,27 @@ object KeyguardBottomAreaViewBinder {
|
||||
|
||||
IconViewBinder.bind(viewModel.icon, view)
|
||||
|
||||
(view.drawable as? Animatable2)?.let { animatable ->
|
||||
(viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
|
||||
// Always start the animation (we do call stop() below, if we need to skip it).
|
||||
animatable.start()
|
||||
|
||||
if (view.tag != iconResourceId) {
|
||||
// Here when we haven't run the animation on a previous update.
|
||||
//
|
||||
// Save the resource ID for next time, so we know not to re-animate the same
|
||||
// animation again.
|
||||
view.tag = iconResourceId
|
||||
} else {
|
||||
// Here when we've already done this animation on a previous update and want to
|
||||
// skip directly to the final frame of the animation to avoid running it.
|
||||
//
|
||||
// By calling stop after start, we go to the final frame of the animation.
|
||||
animatable.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.isActivated = viewModel.isActivated
|
||||
view.drawable.setTint(
|
||||
Utils.getColorAttrDefaultColor(
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||
import com.android.systemui.statusbar.policy.FlashlightController
|
||||
import com.android.systemui.utils.leaks.FakeFlashlightController
|
||||
import com.android.systemui.utils.leaks.LeakCheckedTest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
@@ -38,156 +39,177 @@ import org.junit.runners.JUnit4
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
private lateinit var flashlightController: FakeFlashlightController
|
||||
private lateinit var underTest : FlashlightQuickAffordanceConfig
|
||||
private lateinit var underTest: FlashlightQuickAffordanceConfig
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
injectLeakCheckedDependency(FlashlightController::class.java)
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
|
||||
flashlightController =
|
||||
SysuiLeakCheck().getLeakChecker(FlashlightController::class.java)
|
||||
as FakeFlashlightController
|
||||
underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = false
|
||||
flashlightController.isAvailable = true
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
underTest.onTriggered(null)
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||
assertEquals(R.drawable.ic_flashlight_on,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
||||
assertEquals(
|
||||
R.drawable.qs_flashlight_icon_on,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||
as? Icon.Resource)
|
||||
?.res
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = true
|
||||
flashlightController.isAvailable = true
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
underTest.onTriggered(null)
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||
assertEquals(R.drawable.ic_flashlight_off,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
||||
assertEquals(
|
||||
R.drawable.qs_flashlight_icon_off,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||
as? Icon.Resource)
|
||||
?.res
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = true
|
||||
flashlightController.isAvailable = false
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
flashlightController.onFlashlightError()
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||
assertEquals(R.drawable.ic_flashlight_off,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
||||
assertEquals(
|
||||
R.drawable.qs_flashlight_icon_off,
|
||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||
as? Icon.Resource)
|
||||
?.res
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight availability now off -- hidden`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = true
|
||||
flashlightController.isAvailable = false
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
flashlightController.onFlashlightAvailabilityChanged(false)
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = true
|
||||
flashlightController.isAvailable = false
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
flashlightController.onFlashlightAvailabilityChanged(true)
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||
assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
|
||||
assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
|
||||
assertTrue(
|
||||
(lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
|
||||
is ActivationState.Active
|
||||
)
|
||||
assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isEnabled = false
|
||||
flashlightController.isAvailable = false
|
||||
val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
|
||||
val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
|
||||
|
||||
//when
|
||||
// when
|
||||
flashlightController.onFlashlightAvailabilityChanged(true)
|
||||
val lastValue = values.last()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||
assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
|
||||
assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
|
||||
assertTrue(
|
||||
(lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
|
||||
is ActivationState.Inactive
|
||||
)
|
||||
assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight available -- picker state default`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isAvailable = true
|
||||
|
||||
//when
|
||||
// when
|
||||
val result = underTest.getPickerScreenState()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flashlight not available -- picker state unavailable`() = runTest {
|
||||
//given
|
||||
// given
|
||||
flashlightController.isAvailable = false
|
||||
|
||||
//when
|
||||
// when
|
||||
val result = underTest.getPickerScreenState()
|
||||
|
||||
//then
|
||||
// then
|
||||
assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user