Controls UI - Reuse behaviors

SysUI could crash when adding/removing listeners while state was being
refereshed. No longer add new behaviors on every state change. Keep
change of any existing behavior, and only create a new instance if
necessary.

Bug: 149577051
Test: Use togglerange over and over
Change-Id: I28a71ada9f1c26e2077b1c7ab2f843667bc071ff
This commit is contained in:
Matt Pietal
2020-02-14 19:37:57 -05:00
parent 60892133a4
commit b582b6936b
8 changed files with 110 additions and 54 deletions

View File

@@ -16,6 +16,20 @@
package com.android.systemui.controls.ui
/**
* All controls need to respond to changes in state and handle user-generated events.
* Implementations of this interface provide these different means by adding their own
* event handlers, and will update the control ui as they see fit.
*/
interface Behavior {
fun apply(cvh: ControlViewHolder, cws: ControlWithState)
/**
* Only called once per instance
*/
fun initialize(cvh: ControlViewHolder)
/**
* Will be invoked on every update provided to the Control
*/
fun bind(cws: ControlWithState)
}

View File

@@ -37,6 +37,8 @@ import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.R
import kotlin.reflect.KClass
private const val UPDATE_DELAY_IN_MILLIS = 3000L
class ControlViewHolder(
@@ -54,6 +56,7 @@ class ControlViewHolder(
val clipLayer: ClipDrawable
lateinit var cws: ControlWithState
var cancelUpdate: Runnable? = null
var behavior: Behavior? = null
init {
val ld = layout.getBackground() as LayerDrawable
@@ -83,7 +86,14 @@ class ControlViewHolder(
})
}
findBehavior(status, template).apply(this, cws)
val clazz = findBehavior(status, template)
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)
}
behavior?.bind(cws)
}
fun actionResponse(@ControlAction.ResponseResult response: Int) {
@@ -115,21 +125,14 @@ class ControlViewHolder(
controlsController.action(cws.ci, action)
}
private fun findBehavior(status: Int, template: ControlTemplate): Behavior {
private fun findBehavior(status: Int, template: ControlTemplate): KClass<out Behavior> {
return when {
status == Control.STATUS_UNKNOWN -> UnknownBehavior()
template is ToggleTemplate -> ToggleBehavior()
template is ToggleRangeTemplate -> ToggleRangeBehavior()
template is TemperatureControlTemplate -> TemperatureControlBehavior()
template is ThumbnailTemplate -> StaticBehavior(uiExecutor, bgExecutor)
else -> {
object : Behavior {
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
cvh.status.setText(cws.control?.getStatusText())
cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
}
}
}
status == Control.STATUS_UNKNOWN -> UnknownBehavior::class
template is ToggleTemplate -> ToggleBehavior::class
template is ToggleRangeTemplate -> ToggleRangeBehavior::class
template is TemperatureControlTemplate -> TemperatureControlBehavior::class
template is ThumbnailTemplate -> StaticBehavior::class
else -> DefaultBehavior::class
}
}
@@ -142,9 +145,12 @@ class ControlViewHolder(
icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
icon.setImageTintList(fg)
clipLayer.getDrawable().setTintBlendMode(BlendMode.HUE)
clipLayer.getDrawable().setTintList(bg)
clipLayer.getDrawable().apply {
setTintBlendMode(BlendMode.HUE)
setTintList(bg)
}
}
fun setEnabled(enabled: Boolean) {
status.setEnabled(enabled)
icon.setEnabled(enabled)

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.ui
class DefaultBehavior : Behavior {
lateinit var cvh: ControlViewHolder
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
}
override fun bind(cws: ControlWithState) {
cvh.status.setText(cws.control?.getStatusText() ?: "")
cvh.setEnabled(false)
cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
}
}

View File

@@ -24,20 +24,20 @@ import android.service.controls.templates.ThumbnailTemplate
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
import java.util.concurrent.Executor
/**
* Used for controls that cannot be interacted with. Information is presented to the user
* but no actions can be taken. If using a ThumbnailTemplate, the background image will
* be changed.
*/
class StaticBehavior(
val uiExecutor: Executor,
val bgExecutor: Executor
) : Behavior {
class StaticBehavior() : Behavior {
lateinit var control: Control
lateinit var cvh: ControlViewHolder
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
}
override fun bind(cws: ControlWithState) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
@@ -51,12 +51,12 @@ class StaticBehavior(
val template = control.getControlTemplate()
if (template is ThumbnailTemplate) {
bgExecutor.execute {
cvh.bgExecutor.execute {
// clear the default tinting in favor of only using alpha
val drawable = template.getThumbnail().loadDrawable(cvh.context)
drawable.setTintList(null)
drawable.setAlpha((0.45 * 255).toInt())
uiExecutor.execute {
cvh.uiExecutor.execute {
val radius = cvh.context.getResources()
.getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
clipLayer.setDrawable(CornerDrawable(drawable, radius))

View File

@@ -16,12 +16,10 @@
package com.android.systemui.controls.ui
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.templates.TemperatureControlTemplate
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
@@ -32,15 +30,15 @@ class TemperatureControlBehavior : Behavior {
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var template: TemperatureControlTemplate
lateinit var status: TextView
lateinit var context: Context
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
this.control = cws.control!!
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
status = cvh.status
}
status.setText(control.getStatusText())
override fun bind(cws: ControlWithState) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)

View File

@@ -16,11 +16,9 @@
package com.android.systemui.controls.ui
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.view.View
import android.widget.TextView
import android.service.controls.Control
import android.service.controls.templates.ToggleTemplate
@@ -33,21 +31,21 @@ class ToggleBehavior : Behavior {
lateinit var template: ToggleTemplate
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var context: Context
lateinit var status: TextView
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
this.control = cws.control!!
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
status = cvh.status
status.setText(control.getStatusText())
template = control.getControlTemplate() as ToggleTemplate
cvh.setEnabled(false)
cvh.layout.setOnClickListener(View.OnClickListener() {
ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
})
}
override fun bind(cws: ControlWithState) {
this.control = cws.control!!
cvh.status.setText(control.getStatusText())
template = control.getControlTemplate() as ToggleTemplate
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)

View File

@@ -51,17 +51,13 @@ class ToggleRangeBehavior : Behavior {
private const val DEFAULT_FORMAT = "%.1f"
}
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
this.control = cws.control!!
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
statusExtra = cvh.statusExtra
status = cvh.status
status.setText(control.getStatusText())
context = status.getContext()
cvh.setEnabled(false)
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
cvh.layout.setOnTouchListener({ v: View, e: MotionEvent ->
@@ -77,6 +73,13 @@ class ToggleRangeBehavior : Behavior {
return@setOnTouchListener false
})
}
override fun bind(cws: ControlWithState) {
this.control = cws.control!!
statusExtra = cvh.statusExtra
status.setText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)

View File

@@ -17,7 +17,13 @@
package com.android.systemui.controls.ui
class UnknownBehavior : Behavior {
override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
lateinit var cvh: ControlViewHolder
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
}
override fun bind(cws: ControlWithState) {
cvh.status.setText(cvh.context.getString(com.android.internal.R.string.loading))
cvh.setEnabled(false)
cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))