Controls UI - Support RangeTemplate and TemperatureControlTemplate

Support RangeTemplate within ToggleRangeSupport by removing toggle
functionality. Add back the ability to drag on a device that is off,
as the recently defined behavior specifies that it should turn the
device on. Fully support sub templates of TemperatureControlTemplate.

Bug: 155494213
Test: mock app, use AC_UNIT and BLINDS
Change-Id: Ifd8ee67956325358de7b035dfce27faab9fe49ce
This commit is contained in:
Matt Pietal
2020-05-07 16:50:04 -04:00
parent 2f16758a0a
commit cf51613d3e
8 changed files with 164 additions and 72 deletions

View File

@@ -30,6 +30,9 @@ interface Behavior {
/**
* Will be invoked on every update provided to the Control
*
* @param cws ControlWithState, as loaded from favorites and/or the application
* @param colorOffset An additional flag to control rendering color. See [RenderInfo]
*/
fun bind(cws: ControlWithState)
fun bind(cws: ControlWithState, colorOffset: Int = 0)
}

View File

@@ -28,6 +28,7 @@ import android.service.controls.Control
import android.service.controls.DeviceTypes
import android.service.controls.actions.ControlAction
import android.service.controls.templates.ControlTemplate
import android.service.controls.templates.RangeTemplate
import android.service.controls.templates.StatelessTemplate
import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
@@ -69,6 +70,25 @@ class ControlViewHolder(
const val MIN_LEVEL = 0
const val MAX_LEVEL = 10000
fun findBehaviorClass(
status: Int,
template: ControlTemplate,
deviceType: Int
): KClass<out Behavior> {
return when {
status == Control.STATUS_UNKNOWN -> StatusBehavior::class
status == Control.STATUS_ERROR -> StatusBehavior::class
status == Control.STATUS_NOT_FOUND -> StatusBehavior::class
deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
template is ToggleTemplate -> ToggleBehavior::class
template is StatelessTemplate -> TouchBehavior::class
template is ToggleRangeTemplate -> ToggleRangeBehavior::class
template is RangeTemplate -> ToggleRangeBehavior::class
template is TemperatureControlTemplate -> TemperatureControlBehavior::class
else -> DefaultBehavior::class
}
}
}
private val toggleBackgroundIntensity: Float = layout.context.resources
@@ -123,18 +143,7 @@ class ControlViewHolder(
})
}
val clazz = findBehavior(controlStatus, template, deviceType)
if (behavior == null || behavior!!::class != clazz) {
// Behavior changes can signal a change in template from the app or
// first time setup
behavior = clazz.java.newInstance()
behavior?.initialize(this)
// let behaviors define their own, if necessary, and clear any existing ones
layout.setAccessibilityDelegate(null)
}
behavior?.bind(cws)
behavior = bindBehavior(behavior, findBehaviorClass(controlStatus, template, deviceType))
layout.setContentDescription("${title.text} ${subtitle.text} ${status.text}")
}
@@ -190,25 +199,30 @@ class ControlViewHolder(
fun usePanel(): Boolean = deviceType in ControlViewHolder.FORCE_PANEL_DEVICES
private fun findBehavior(
status: Int,
template: ControlTemplate,
deviceType: Int
): KClass<out Behavior> {
return when {
status == Control.STATUS_UNKNOWN -> StatusBehavior::class
status == Control.STATUS_ERROR -> StatusBehavior::class
status == Control.STATUS_NOT_FOUND -> StatusBehavior::class
deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
template is ToggleTemplate -> ToggleBehavior::class
template is StatelessTemplate -> TouchBehavior::class
template is ToggleRangeTemplate -> ToggleRangeBehavior::class
template is TemperatureControlTemplate -> TemperatureControlBehavior::class
else -> DefaultBehavior::class
fun bindBehavior(
existingBehavior: Behavior?,
clazz: KClass<out Behavior>,
offset: Int = 0
): Behavior {
val behavior = if (existingBehavior == null || existingBehavior!!::class != clazz) {
// Behavior changes can signal a change in template from the app or
// first time setup
val newBehavior = clazz.java.newInstance()
newBehavior.initialize(this)
// let behaviors define their own, if necessary, and clear any existing ones
layout.setAccessibilityDelegate(null)
newBehavior
} else {
existingBehavior
}
return behavior.also {
it.bind(cws, offset)
}
}
internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0, animated: Boolean = true) {
internal fun applyRenderInfo(enabled: Boolean, offset: Int, animated: Boolean = true) {
setEnabled(enabled)
val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)

View File

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

View File

@@ -27,7 +27,7 @@ class StatusBehavior : Behavior {
this.cvh = cvh
}
override fun bind(cws: ControlWithState) {
override fun bind(cws: ControlWithState, colorOffset: Int) {
val status = cws.control?.status ?: Control.STATUS_UNKNOWN
val msg = when (status) {
Control.STATUS_ERROR -> R.string.controls_error_generic
@@ -35,6 +35,6 @@ class StatusBehavior : Behavior {
else -> com.android.internal.R.string.loading
}
cvh.status.setText(cvh.context.getString(msg))
cvh.applyRenderInfo(false)
cvh.applyRenderInfo(false, colorOffset)
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.controls.ui
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.templates.ControlTemplate
import android.service.controls.templates.TemperatureControlTemplate
import com.android.systemui.R
@@ -29,17 +30,13 @@ class TemperatureControlBehavior : Behavior {
lateinit var clipLayer: Drawable
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var template: TemperatureControlTemplate
var subBehavior: Behavior? = null
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
cvh.layout.setOnClickListener { _ ->
cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
}
}
override fun bind(cws: ControlWithState) {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
@@ -47,11 +44,32 @@ class TemperatureControlBehavior : Behavior {
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
template = control.getControlTemplate() as TemperatureControlTemplate
val template = control.getControlTemplate() as TemperatureControlTemplate
val activeMode = template.getCurrentActiveMode()
val enabled = activeMode != 0 && activeMode != TemperatureControlTemplate.MODE_OFF
clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL)
cvh.applyRenderInfo(enabled, activeMode)
val subTemplate = template.getTemplate()
if (subTemplate == ControlTemplate.getNoTemplateObject() ||
subTemplate == ControlTemplate.getErrorTemplate()) {
// No sub template is specified, apply a default look with basic touch interaction.
// Treat an error as no template.
val enabled = activeMode != 0 && activeMode != TemperatureControlTemplate.MODE_OFF
clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL)
cvh.applyRenderInfo(enabled, activeMode)
cvh.layout.setOnClickListener { _ ->
cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
}
} else {
// A sub template has been specified, use this as the default behavior for user
// interactions (touch, range)
subBehavior = cvh.bindBehavior(
subBehavior,
ControlViewHolder.findBehaviorClass(
control.status,
subTemplate,
control.deviceType
),
activeMode
)
}
}
}

View File

@@ -19,7 +19,9 @@ package com.android.systemui.controls.ui
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleTemplate
import android.util.Log
import android.view.View
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
@@ -39,17 +41,25 @@ class ToggleBehavior : Behavior {
})
}
override fun bind(cws: ControlWithState) {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
template = control.getControlTemplate() as ToggleTemplate
val controlTemplate = control.getControlTemplate()
template = when (controlTemplate) {
is ToggleTemplate -> controlTemplate
is TemperatureControlTemplate -> controlTemplate.getTemplate() as ToggleTemplate
else -> {
Log.e(ControlsUiController.TAG, "Unsupported template type: $controlTemplate")
return
}
}
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.level = MAX_LEVEL
val checked = template.isChecked()
cvh.applyRenderInfo(checked)
cvh.applyRenderInfo(checked, colorOffset)
}
}

View File

@@ -25,7 +25,9 @@ import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.service.controls.Control
import android.service.controls.actions.FloatAction
import android.service.controls.templates.ControlTemplate
import android.service.controls.templates.RangeTemplate
import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.util.Log
import android.util.MathUtils
@@ -44,10 +46,14 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
import java.util.IllegalFormatException
/**
* Supports [ToggleRangeTemplate] and [RangeTemplate], as well as when one of those templates is
* defined as the subtemplate in [TemperatureControlTemplate].
*/
class ToggleRangeBehavior : Behavior {
private var rangeAnimator: ValueAnimator? = null
lateinit var clipLayer: Drawable
lateinit var template: ToggleRangeTemplate
lateinit var templateId: String
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var rangeTemplate: RangeTemplate
@@ -55,6 +61,9 @@ class ToggleRangeBehavior : Behavior {
lateinit var context: Context
var currentStatusText: CharSequence = ""
var currentRangeValue: String = ""
var isChecked: Boolean = false
var isToggleable: Boolean = false
var colorOffset: Int = 0
companion object {
private const val DEFAULT_FORMAT = "%.1f"
@@ -65,7 +74,7 @@ class ToggleRangeBehavior : Behavior {
status = cvh.status
context = status.getContext()
cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.applyRenderInfo(false /* enabled */, colorOffset, false /* animated */)
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
@@ -86,8 +95,40 @@ class ToggleRangeBehavior : Behavior {
}
}
override fun bind(cws: ControlWithState) {
private fun setup(template: ToggleRangeTemplate) {
rangeTemplate = template.getRange()
isToggleable = true
isChecked = template.isChecked()
}
private fun setup(template: RangeTemplate) {
rangeTemplate = template
// only show disabled state when value is at the minimum
isChecked = rangeTemplate.currentValue != rangeTemplate.minValue
}
private fun setupTemplate(template: ControlTemplate): Boolean {
return when (template) {
is ToggleRangeTemplate -> {
setup(template)
true
}
is RangeTemplate -> {
setup(template)
true
}
is TemperatureControlTemplate -> setupTemplate(template.getTemplate())
else -> {
Log.e(ControlsUiController.TAG, "Unsupported template type: $template")
false
}
}
}
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
this.colorOffset = colorOffset
currentStatusText = control.getStatusText()
status.setText(currentStatusText)
@@ -99,13 +140,14 @@ class ToggleRangeBehavior : Behavior {
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
template = control.getControlTemplate() as ToggleRangeTemplate
rangeTemplate = template.getRange()
val template = control.getControlTemplate()
if (!setupTemplate(template)) return
templateId = template.getTemplateId()
val checked = template.isChecked()
updateRange(rangeToLevelValue(rangeTemplate.currentValue), checked, /* isDragging */ false)
updateRange(rangeToLevelValue(rangeTemplate.currentValue), isChecked,
/* isDragging */ false)
cvh.applyRenderInfo(checked)
cvh.applyRenderInfo(isChecked, colorOffset)
/*
* This is custom widget behavior, so add a new accessibility delegate to
@@ -141,9 +183,12 @@ class ToggleRangeBehavior : Behavior {
): Boolean {
val handled = when (action) {
AccessibilityNodeInfo.ACTION_CLICK -> {
cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(),
template.isChecked())
true
if (!isToggleable) {
false
} else {
cvh.controlActionCoordinator.toggle(cvh, templateId, isChecked)
true
}
}
AccessibilityNodeInfo.ACTION_LONG_CLICK -> {
cvh.controlActionCoordinator.longPress(cvh)
@@ -157,7 +202,7 @@ class ToggleRangeBehavior : Behavior {
val value = arguments.getFloat(
AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)
val level = rangeToLevelValue(value)
updateRange(level, template.isChecked(), /* isDragging */ true)
updateRange(level, isChecked, /* isDragging */ true)
endUpdateRange()
true
}
@@ -182,7 +227,14 @@ class ToggleRangeBehavior : Behavior {
}
fun updateRange(level: Int, checked: Boolean, isDragging: Boolean) {
val newLevel = if (checked) Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level)) else MIN_LEVEL
val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level))
// If the current level is at the minimum and the user is dragging, set the control to
// the enabled state to indicate their intention to enable the device. This will update
// control colors to support dragging.
if (clipLayer.level == MIN_LEVEL && newLevel > MIN_LEVEL) {
cvh.applyRenderInfo(checked, colorOffset, false /* animated */)
}
rangeAnimator?.cancel()
if (isDragging) {
@@ -282,7 +334,7 @@ class ToggleRangeBehavior : Behavior {
if (isDragging) {
return
}
cvh.controlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
cvh.controlActionCoordinator.longPress(cvh)
}
override fun onScroll(
@@ -291,26 +343,21 @@ class ToggleRangeBehavior : Behavior {
xDiff: Float,
yDiff: Float
): Boolean {
if (!template.isChecked) {
return false
}
if (!isDragging) {
v.getParent().requestDisallowInterceptTouchEvent(true)
this@ToggleRangeBehavior.beginUpdateRange()
beginUpdateRange()
isDragging = true
}
val ratioDiff = -xDiff / v.width
val changeAmount = ((MAX_LEVEL - MIN_LEVEL) * ratioDiff).toInt()
this@ToggleRangeBehavior.updateRange(clipLayer.level + changeAmount,
checked = true, isDragging = true)
updateRange(clipLayer.level + changeAmount, checked = true, isDragging = true)
return true
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
val th = this@ToggleRangeBehavior
cvh.controlActionCoordinator.toggle(th.cvh, th.template.getTemplateId(),
th.template.isChecked())
if (!isToggleable) return false
cvh.controlActionCoordinator.toggle(cvh, templateId, isChecked)
return true
}
}

View File

@@ -44,7 +44,7 @@ class TouchBehavior : Behavior {
})
}
override fun bind(cws: ControlWithState) {
override fun bind(cws: ControlWithState, colorOffset: Int) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
template = control.getControlTemplate()
@@ -53,6 +53,6 @@ class TouchBehavior : Behavior {
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.setLevel(MIN_LEVEL)
cvh.applyRenderInfo(false)
cvh.applyRenderInfo(false, colorOffset)
}
}