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 275c7788edd3b..842c39b20906a 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 @@ 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) } 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 2653ce0423b80..8dc3971925b2a 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.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 { + 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 { - 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, + 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 e850a6a559d68..722ade923208c 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 @@ 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) } } 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 49c44088ce3d2..d8dceba352516 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 @@ 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) } } 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 b4d0e63496052..2795c7af6c828 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 @@ 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 + ) + } } } 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 3e1669898a7ff..c432c09eeda42 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 @@ 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) } } 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 3dc0ff36339ae..a09ed09bfcec4 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.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 } } 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 7ae3df7514199..8ce2e617669ce 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 @@ 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) } }