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
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
index 275c778..842c39b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
@@ -30,6 +30,9 @@
/**
* 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)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 2653ce0..8dc3971 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -28,6 +28,7 @@
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 @@
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 @@
})
}
- 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 @@
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)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
index e850a6a..722ade9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
@@ -23,8 +23,8 @@
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)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 49c4408..d8dceba 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -27,7 +27,7 @@
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 @@
else -> com.android.internal.R.string.loading
}
cvh.status.setText(cvh.context.getString(msg))
- cvh.applyRenderInfo(false)
+ cvh.applyRenderInfo(false, colorOffset)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index b4d0e63..2795c7a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -19,6 +19,7 @@
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 @@
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 @@
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
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index 3e16698..c432c09 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -19,7 +19,9 @@
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 @@
})
}
- 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)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 3dc0ff3..a09ed09 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -25,7 +25,9 @@
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.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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
): 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 @@
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 @@
}
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 @@
if (isDragging) {
return
}
- cvh.controlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
+ cvh.controlActionCoordinator.longPress(cvh)
}
override fun onScroll(
@@ -291,26 +343,21 @@
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
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
index 7ae3df7..8ce2e61 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -44,7 +44,7 @@
})
}
- 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 @@
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.setLevel(MIN_LEVEL)
- cvh.applyRenderInfo(false)
+ cvh.applyRenderInfo(false, colorOffset)
}
}