Merge "Support animatable icons to quick affordances." into tm-qpr-dev am: e155f5c70b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20592357 Change-Id: I045409ab58b19ee8f2aebe100a5bb84108a4fe0f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -21,19 +21,21 @@ import android.content.Context
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
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.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
import com.android.systemui.common.shared.model.ContentDescription
|
import com.android.systemui.common.shared.model.ContentDescription
|
||||||
import com.android.systemui.common.shared.model.Icon
|
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.keyguard.shared.quickaffordance.ActivationState
|
||||||
import com.android.systemui.statusbar.policy.FlashlightController
|
import com.android.systemui.statusbar.policy.FlashlightController
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class FlashlightQuickAffordanceConfig @Inject constructor(
|
class FlashlightQuickAffordanceConfig
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
@Application private val context: Context,
|
@Application private val context: Context,
|
||||||
private val flashlightController: FlashlightController,
|
private val flashlightController: FlashlightController,
|
||||||
) : KeyguardQuickAffordanceConfig {
|
) : KeyguardQuickAffordanceConfig {
|
||||||
@@ -46,7 +48,7 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
|||||||
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
||||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||||
Icon.Resource(
|
Icon.Resource(
|
||||||
R.drawable.ic_flashlight_on,
|
R.drawable.qs_flashlight_icon_on,
|
||||||
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
||||||
),
|
),
|
||||||
ActivationState.Active
|
ActivationState.Active
|
||||||
@@ -57,7 +59,7 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
|||||||
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
|
||||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||||
Icon.Resource(
|
Icon.Resource(
|
||||||
R.drawable.ic_flashlight_off,
|
R.drawable.qs_flashlight_icon_off,
|
||||||
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
ContentDescription.Resource(R.string.quick_settings_flashlight_label)
|
||||||
),
|
),
|
||||||
ActivationState.Inactive
|
ActivationState.Inactive
|
||||||
@@ -77,15 +79,12 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
|||||||
get() = context.getString(R.string.quick_settings_flashlight_label)
|
get() = context.getString(R.string.quick_settings_flashlight_label)
|
||||||
|
|
||||||
override val pickerIconResourceId: Int
|
override val pickerIconResourceId: Int
|
||||||
get() = if (flashlightController.isEnabled) {
|
get() = R.drawable.ic_flashlight_off
|
||||||
R.drawable.ic_flashlight_on
|
|
||||||
} else {
|
|
||||||
R.drawable.ic_flashlight_off
|
|
||||||
}
|
|
||||||
|
|
||||||
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
|
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
|
||||||
conflatedCallbackFlow {
|
conflatedCallbackFlow {
|
||||||
val flashlightCallback = object : FlashlightController.FlashlightListener {
|
val flashlightCallback =
|
||||||
|
object : FlashlightController.FlashlightListener {
|
||||||
override fun onFlashlightChanged(enabled: Boolean) {
|
override fun onFlashlightChanged(enabled: Boolean) {
|
||||||
trySendWithFailureLogging(
|
trySendWithFailureLogging(
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -98,7 +97,10 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFlashlightError() {
|
override fun onFlashlightError() {
|
||||||
trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
|
trySendWithFailureLogging(
|
||||||
|
FlashlightState.OffAvailable.toLockScreenState(),
|
||||||
|
TAG
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFlashlightAvailabilityChanged(available: Boolean) {
|
override fun onFlashlightAvailabilityChanged(available: Boolean) {
|
||||||
@@ -119,15 +121,15 @@ class FlashlightQuickAffordanceConfig @Inject constructor(
|
|||||||
|
|
||||||
flashlightController.addCallback(flashlightCallback)
|
flashlightController.addCallback(flashlightCallback)
|
||||||
|
|
||||||
awaitClose {
|
awaitClose { flashlightController.removeCallback(flashlightCallback) }
|
||||||
flashlightController.removeCallback(flashlightCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTriggered(expandable: Expandable?):
|
override fun onTriggered(
|
||||||
KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
expandable: Expandable?
|
||||||
flashlightController
|
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||||
.setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
|
flashlightController.setFlashlight(
|
||||||
|
flashlightController.isAvailable && !flashlightController.isEnabled
|
||||||
|
)
|
||||||
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
|
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.ui.binder
|
package com.android.systemui.keyguard.ui.binder
|
||||||
|
|
||||||
|
import android.graphics.drawable.Animatable2
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -27,12 +28,11 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor
|
|
||||||
import com.android.keyguard.LockIconViewController
|
|
||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
import com.android.systemui.animation.Interpolators
|
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.common.ui.binder.IconViewBinder
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
||||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
|
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
|
||||||
@@ -73,7 +73,8 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
fun onConfigurationChanged()
|
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
|
fun shouldConstrainToTopOfLockIcon(): Boolean
|
||||||
}
|
}
|
||||||
@@ -248,6 +249,27 @@ object KeyguardBottomAreaViewBinder {
|
|||||||
|
|
||||||
IconViewBinder.bind(viewModel.icon, view)
|
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.isActivated = viewModel.isActivated
|
||||||
view.drawable.setTint(
|
view.drawable.setTint(
|
||||||
Utils.getColorAttrDefaultColor(
|
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.statusbar.policy.FlashlightController
|
||||||
import com.android.systemui.utils.leaks.FakeFlashlightController
|
import com.android.systemui.utils.leaks.FakeFlashlightController
|
||||||
import com.android.systemui.utils.leaks.LeakCheckedTest
|
import com.android.systemui.utils.leaks.LeakCheckedTest
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
@@ -38,6 +39,7 @@ import org.junit.runners.JUnit4
|
|||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
||||||
@@ -51,7 +53,9 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
injectLeakCheckedDependency(FlashlightController::class.java)
|
injectLeakCheckedDependency(FlashlightController::class.java)
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
|
flashlightController =
|
||||||
|
SysuiLeakCheck().getLeakChecker(FlashlightController::class.java)
|
||||||
|
as FakeFlashlightController
|
||||||
underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
|
underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +73,12 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||||
assertEquals(R.drawable.ic_flashlight_on,
|
assertEquals(
|
||||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
R.drawable.qs_flashlight_icon_on,
|
||||||
|
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||||
|
as? Icon.Resource)
|
||||||
|
?.res
|
||||||
|
)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,8 +96,12 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||||
assertEquals(R.drawable.ic_flashlight_off,
|
assertEquals(
|
||||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
R.drawable.qs_flashlight_icon_off,
|
||||||
|
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||||
|
as? Icon.Resource)
|
||||||
|
?.res
|
||||||
|
)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,8 +119,12 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||||
assertEquals(R.drawable.ic_flashlight_off,
|
assertEquals(
|
||||||
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
|
R.drawable.qs_flashlight_icon_off,
|
||||||
|
((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
|
||||||
|
as? Icon.Resource)
|
||||||
|
?.res
|
||||||
|
)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +159,11 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||||
assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
|
assertTrue(
|
||||||
assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
|
(lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
|
||||||
|
is ActivationState.Active
|
||||||
|
)
|
||||||
|
assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +181,11 @@ class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
|
||||||
assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
|
assertTrue(
|
||||||
assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
|
(lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
|
||||||
|
is ActivationState.Inactive
|
||||||
|
)
|
||||||
|
assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user