Merge "Controls UI - Loading animations" into rvc-dev am: 31db4f456b

Change-Id: I83d6d50c6e814c672ceb5da0b522b81dc294fd51
This commit is contained in:
TreeHugger Robot
2020-05-18 15:48:08 +00:00
committed by Automerger Merge Worker
9 changed files with 155 additions and 62 deletions

View File

@@ -18,10 +18,15 @@ package com.android.systemui.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.ColorRes
import android.app.Dialog
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -34,6 +39,7 @@ import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.service.controls.templates.ToggleTemplate
import android.util.MathUtils
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -63,6 +69,8 @@ class ControlViewHolder(
private const val UPDATE_DELAY_IN_MILLIS = 3000L
private const val ALPHA_ENABLED = 255
private const val ALPHA_DISABLED = 0
private const val STATUS_ALPHA_ENABLED = 1f
private const val STATUS_ALPHA_DIMMED = 0.45f
private val FORCE_PANEL_DEVICES = setOf(
DeviceTypes.TYPE_THERMOSTAT,
DeviceTypes.TYPE_CAMERA
@@ -94,9 +102,11 @@ class ControlViewHolder(
private val toggleBackgroundIntensity: Float = layout.context.resources
.getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
private var stateAnimator: ValueAnimator? = null
private var statusAnimator: Animator? = null
private val baseLayer: GradientDrawable
val icon: ImageView = layout.requireViewById(R.id.icon)
val status: TextView = layout.requireViewById(R.id.status)
private val status: TextView = layout.requireViewById(R.id.status)
private var nextStatusText: CharSequence = ""
val title: TextView = layout.requireViewById(R.id.title)
val subtitle: TextView = layout.requireViewById(R.id.subtitle)
val context: Context = layout.getContext()
@@ -105,6 +115,7 @@ class ControlViewHolder(
var cancelUpdate: Runnable? = null
var behavior: Behavior? = null
var lastAction: ControlAction? = null
var isLoading = false
private var lastChallengeDialog: Dialog? = null
private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
@@ -144,6 +155,7 @@ class ControlViewHolder(
})
}
isLoading = false
behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, template, deviceType))
updateContentDescription()
}
@@ -189,11 +201,11 @@ class ControlViewHolder(
val previousText = status.getText()
cancelUpdate = uiExecutor.executeDelayed({
status.setText(previousText)
updateContentDescription()
}, UPDATE_DELAY_IN_MILLIS)
setStatusText(previousText)
updateContentDescription()
}, UPDATE_DELAY_IN_MILLIS)
status.setText(tempStatus)
setStatusText(tempStatus)
updateContentDescription()
}
@@ -231,18 +243,50 @@ class ControlViewHolder(
}
internal fun applyRenderInfo(enabled: Boolean, offset: Int, animated: Boolean = true) {
setEnabled(enabled)
val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
val fg = context.resources.getColorStateList(ri.foreground, context.theme)
val newText = nextStatusText
nextStatusText = ""
val control = cws.control
var shouldAnimate = animated
if (newText == status.text) {
shouldAnimate = false
}
animateStatusChange(shouldAnimate) {
updateStatusRow(enabled, newText, ri.icon, fg, control)
}
animateBackgroundChange(shouldAnimate, enabled, ri.enabledBackground)
}
fun getStatusText() = status.text
fun setStatusTextSize(textSize: Float) =
status.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
fun setStatusText(text: CharSequence, immediately: Boolean = false) {
if (immediately) {
status.alpha = STATUS_ALPHA_ENABLED
status.text = text
nextStatusText = ""
} else {
nextStatusText = text
}
}
private fun animateBackgroundChange(
animated: Boolean,
enabled: Boolean,
@ColorRes bgColor: Int
) {
val bg = context.resources.getColor(R.color.control_default_background, context.theme)
var (newClipColor, newAlpha) = if (enabled) {
// allow color overrides for the enabled state only
val color = cws.control?.getCustomColor()?.let {
val state = intArrayOf(android.R.attr.state_enabled)
it.getColorForState(state, it.getDefaultColor())
} ?: context.resources.getColor(ri.enabledBackground, context.theme)
} ?: context.resources.getColor(bgColor, context.theme)
listOf(color, ALPHA_ENABLED)
} else {
listOf(
@@ -251,21 +295,6 @@ class ControlViewHolder(
)
}
status.setTextColor(fg)
cws.control?.getCustomIcon()?.let {
// do not tint custom icons, assume the intended icon color is correct
icon.imageTintList = null
icon.setImageIcon(it)
} ?: run {
icon.setImageDrawable(ri.icon)
// do not color app icons
if (deviceType != DeviceTypes.TYPE_ROUTINE) {
icon.imageTintList = fg
}
}
(clipLayer.getDrawable() as GradientDrawable).apply {
val newBaseColor = if (behavior is ToggleRangeBehavior) {
ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity)
@@ -303,6 +332,77 @@ class ControlViewHolder(
}
}
private fun animateStatusChange(animated: Boolean, statusRowUpdater: () -> Unit) {
statusAnimator?.cancel()
if (!animated) {
statusRowUpdater.invoke()
return
}
if (isLoading) {
statusRowUpdater.invoke()
statusAnimator = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_DIMMED).apply {
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
duration = 500L
interpolator = Interpolators.LINEAR
startDelay = 900L
start()
}
} else {
val fadeOut = ObjectAnimator.ofFloat(status, "alpha", 0f).apply {
duration = 200L
interpolator = Interpolators.LINEAR
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
statusRowUpdater.invoke()
}
})
}
val fadeIn = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_ENABLED).apply {
duration = 200L
interpolator = Interpolators.LINEAR
}
statusAnimator = AnimatorSet().apply {
playSequentially(fadeOut, fadeIn)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
status.alpha = STATUS_ALPHA_ENABLED
statusAnimator = null
}
})
start()
}
}
}
private fun updateStatusRow(
enabled: Boolean,
text: CharSequence,
drawable: Drawable,
color: ColorStateList,
control: Control?
) {
setEnabled(enabled)
status.text = text
status.setTextColor(color)
control?.getCustomIcon()?.let {
// do not tint custom icons, assume the intended icon color is correct
icon.imageTintList = null
icon.setImageIcon(it)
} ?: run {
icon.setImageDrawable(drawable)
// do not color app icons
if (deviceType != DeviceTypes.TYPE_ROUTINE) {
icon.imageTintList = color
}
}
}
private fun setEnabled(enabled: Boolean) {
status.setEnabled(enabled)
icon.setEnabled(enabled)

View File

@@ -24,7 +24,7 @@ class DefaultBehavior : Behavior {
}
override fun bind(cws: ControlWithState, colorOffset: Int) {
cvh.status.setText(cws.control?.getStatusText() ?: "")
cvh.setStatusText(cws.control?.getStatusText() ?: "")
cvh.applyRenderInfo(false, colorOffset)
}
}

View File

@@ -97,6 +97,8 @@ private const val BUCKET_SIZE = 1000
private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE
private val deviceColorMap = mapOf<Int, Pair<Int, Int>>(
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to
Pair(R.color.control_default_foreground, R.color.control_default_background),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to
Pair(R.color.thermo_heat_foreground, R.color.control_enabled_thermo_heat_background),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to
@@ -108,13 +110,9 @@ private val deviceColorMap = mapOf<Int, Pair<Int, Int>>(
}
private val deviceIconMap = mapOf<Int, IconState>(
THERMOSTAT_RANGE to IconState(
R.drawable.ic_device_thermostat_off,
R.drawable.ic_device_thermostat_on
),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to IconState(
R.drawable.ic_device_thermostat_off,
R.drawable.ic_device_thermostat_on
R.drawable.ic_device_thermostat_off
),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to IconState(
R.drawable.ic_device_thermostat_off,
@@ -130,7 +128,7 @@ private val deviceIconMap = mapOf<Int, IconState>(
),
(THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_ECO) to IconState(
R.drawable.ic_device_thermostat_off,
R.drawable.ic_device_thermostat_on
R.drawable.ic_device_thermostat_off
),
DeviceTypes.TYPE_THERMOSTAT to IconState(
R.drawable.ic_device_thermostat_off,

View File

@@ -32,9 +32,12 @@ class StatusBehavior : Behavior {
val msg = when (status) {
Control.STATUS_ERROR -> R.string.controls_error_generic
Control.STATUS_NOT_FOUND -> R.string.controls_error_removed
else -> com.android.internal.R.string.loading
else -> {
cvh.isLoading = true
com.android.internal.R.string.loading
}
}
cvh.status.setText(cvh.context.getString(msg))
cvh.setStatusText(cvh.context.getString(msg))
cvh.applyRenderInfo(false, colorOffset)
}
}

View File

@@ -39,7 +39,7 @@ class TemperatureControlBehavior : Behavior {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
cvh.setStatusText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)

View File

@@ -34,7 +34,6 @@ class ToggleBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
@@ -44,7 +43,7 @@ class ToggleBehavior : Behavior {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
cvh.setStatusText(control.getStatusText())
val controlTemplate = control.getControlTemplate()
template = when (controlTemplate) {
is ToggleTemplate -> controlTemplate

View File

@@ -31,7 +31,6 @@ import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.util.Log
import android.util.MathUtils
import android.util.TypedValue
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
@@ -39,7 +38,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
@@ -57,7 +55,6 @@ class ToggleRangeBehavior : Behavior {
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var rangeTemplate: RangeTemplate
lateinit var status: TextView
lateinit var context: Context
var currentStatusText: CharSequence = ""
var currentRangeValue: String = ""
@@ -71,10 +68,7 @@ class ToggleRangeBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
status = cvh.status
context = status.getContext()
cvh.applyRenderInfo(false /* enabled */, colorOffset, false /* animated */)
context = cvh.context
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
@@ -131,7 +125,6 @@ class ToggleRangeBehavior : Behavior {
this.colorOffset = colorOffset
currentStatusText = control.getStatusText()
status.setText(currentStatusText)
// ControlViewHolder sets a long click listener, but we want to handle touch in
// here instead, otherwise we'll have state conflicts.
@@ -222,7 +215,7 @@ class ToggleRangeBehavior : Behavior {
}
fun beginUpdateRange() {
status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
cvh.setStatusTextSize(context.getResources()
.getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
}
@@ -261,14 +254,13 @@ class ToggleRangeBehavior : Behavior {
val newValue = levelToRangeValue(newLevel)
currentRangeValue = format(rangeTemplate.getFormatString().toString(),
DEFAULT_FORMAT, newValue)
val text = if (isDragging) {
currentRangeValue
if (isDragging) {
cvh.setStatusText(currentRangeValue, /* immediately */ true)
} else {
"$currentStatusText $currentRangeValue"
cvh.setStatusText("$currentStatusText $currentRangeValue")
}
status.setText(text)
} else {
status.setText(currentStatusText)
cvh.setStatusText(currentStatusText)
}
}
@@ -296,9 +288,9 @@ class ToggleRangeBehavior : Behavior {
}
fun endUpdateRange() {
status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
cvh.setStatusTextSize(context.getResources()
.getDimensionPixelSize(R.dimen.control_status_normal).toFloat())
status.setText("$currentStatusText $currentRangeValue")
cvh.setStatusText("$currentStatusText $currentRangeValue", /* immediately */ true)
cvh.action(FloatAction(rangeTemplate.getTemplateId(),
findNearestStep(levelToRangeValue(clipLayer.getLevel()))))
}

View File

@@ -23,6 +23,7 @@ import android.service.controls.Control
import android.service.controls.templates.ControlTemplate
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
/**
@@ -37,7 +38,6 @@ class TouchBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
@@ -46,13 +46,14 @@ class TouchBehavior : Behavior {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
cvh.setStatusText(control.getStatusText())
template = control.getControlTemplate()
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.setLevel(MIN_LEVEL)
cvh.applyRenderInfo(false, colorOffset)
val enabled = if (colorOffset > 0) true else false
clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL)
cvh.applyRenderInfo(enabled, colorOffset)
}
}

View File

@@ -2221,7 +2221,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mBackgroundDrawable.setAlpha(0);
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
ObjectAnimator alphaAnimator =
ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 0f, 1f);
ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
alphaAnimator.setDuration(183);
alphaAnimator.addUpdateListener((animation) -> {
@@ -2234,8 +2234,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
alphaAnimator.setDuration(350);
xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
xAnimator.setDuration(350);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaAnimator, xAnimator);
@@ -2247,7 +2247,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
dismissWithAnimation(() -> {
mContainer.setTranslationX(0);
ObjectAnimator alphaAnimator =
ObjectAnimator.ofFloat(mContainer, "transitionAlpha", 1f, 0f);
ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
alphaAnimator.setDuration(233);
alphaAnimator.addUpdateListener((animation) -> {
@@ -2261,8 +2261,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
alphaAnimator.setDuration(350);
xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
xAnimator.setDuration(350);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaAnimator, xAnimator);