From 547299df07c42cb11bad561dd005710c99210ccb Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Wed, 9 Dec 2020 09:51:18 -0500 Subject: [PATCH] Controls UI - Part 1 - Thumbnail support Add dedicated support for displaying background images on controls. Apply a basic scrim to the image as well as a shadow on the status text in order to keep the text legible. More work is needed to add a shadow to the icon. Bug: 175200791 Test: manual, use Mock app with camera Change-Id: Ib76704e6577d3335fc4a563280aa15429f9dd277 --- .../SystemUI/res/color/camera_foreground.xml | 23 ++++ packages/SystemUI/res/values/colors.xml | 2 + packages/SystemUI/res/values/dimens.xml | 4 + .../systemui/controls/ui/ControlViewHolder.kt | 35 ++++-- .../systemui/controls/ui/RenderInfo.kt | 4 +- .../systemui/controls/ui/ThumbnailBehavior.kt | 101 ++++++++++++++++++ 6 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 packages/SystemUI/res/color/camera_foreground.xml create mode 100644 packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt diff --git a/packages/SystemUI/res/color/camera_foreground.xml b/packages/SystemUI/res/color/camera_foreground.xml new file mode 100644 index 0000000000000..6e039cc1df1ef --- /dev/null +++ b/packages/SystemUI/res/color/camera_foreground.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index c51e0bf2c31ba..afbe9b85b9f91 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -257,6 +257,8 @@ #FF8B66 @color/GM2_blue_300 @color/GM2_blue_300 + #33000000 + @*android:color/black #F28B82 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 57e1d43bc6ef0..be5dcf67ac1d3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1269,6 +1269,10 @@ 2dp 32dp + 2.0 + 2.0 + 2.0 + @dimen/control_base_item_margin 15 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 0e4f68431c160..9d92a40beec47 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -37,6 +37,7 @@ 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.ThumbnailTemplate import android.service.controls.templates.ToggleRangeTemplate import android.service.controls.templates.ToggleTemplate import android.util.MathUtils @@ -87,8 +88,11 @@ class ControlViewHolder( ): KClass { return when { status != Control.STATUS_OK -> StatusBehavior::class - deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class + template is ThumbnailTemplate -> ThumbnailBehavior::class + + // Required for legacy support, or where cameras do not use the new template + deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class template is ToggleTemplate -> ToggleBehavior::class template is StatelessTemplate -> TouchBehavior::class template is ToggleRangeTemplate -> ToggleRangeBehavior::class @@ -105,7 +109,7 @@ class ControlViewHolder( private var statusAnimator: Animator? = null private val baseLayer: GradientDrawable val icon: ImageView = layout.requireViewById(R.id.icon) - private val status: TextView = layout.requireViewById(R.id.status) + 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) @@ -132,7 +136,6 @@ class ControlViewHolder( val ld = layout.getBackground() as LayerDrawable ld.mutate() clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable - clipLayer.alpha = ALPHA_DISABLED baseLayer = ld.findDrawableByLayerId(R.id.background) as GradientDrawable // needed for marquee to start status.setSelected(true) @@ -315,7 +318,9 @@ class ControlViewHolder( ) } - (clipLayer.getDrawable() as GradientDrawable).apply { + clipLayer.getDrawable().apply { + clipLayer.alpha = ALPHA_DISABLED + val newBaseColor = if (behavior is ToggleRangeBehavior) { ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity) } else { @@ -323,15 +328,25 @@ class ControlViewHolder( } stateAnimator?.cancel() if (animated) { - val oldColor = color?.defaultColor ?: newClipColor + val oldColor = if (this is GradientDrawable) { + this.color?.defaultColor ?: newClipColor + } else { + newClipColor + } val oldBaseColor = baseLayer.color?.defaultColor ?: newBaseColor val oldAlpha = layout.alpha + + // Animate both alpha and background colors. Only animate colors for + // GradientDrawables and not static images as used for the ThumbnailTemplate. stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply { addUpdateListener { alpha = it.animatedValue as Int - setColor(ColorUtils.blendARGB(oldColor, newClipColor, it.animatedFraction)) - baseLayer.setColor(ColorUtils.blendARGB(oldBaseColor, - newBaseColor, it.animatedFraction)) + if (this is GradientDrawable) { + this.setColor(ColorUtils.blendARGB(oldColor, newClipColor, + it.animatedFraction)) + } + baseLayer.setColor(ColorUtils.blendARGB(oldBaseColor, newBaseColor, + it.animatedFraction)) layout.alpha = MathUtils.lerp(oldAlpha, 1f, it.animatedFraction) } addListener(object : AnimatorListenerAdapter() { @@ -345,7 +360,9 @@ class ControlViewHolder( } } else { alpha = newAlpha - setColor(newClipColor) + if (this is GradientDrawable) { + this.setColor(newClipColor) + } baseLayer.setColor(newBaseColor) layout.alpha = 1f } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index 3f17a4fefd7e8..ad2b785ea5c56 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -92,7 +92,9 @@ private val deviceColorMap = mapOf>( (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to Pair(R.color.thermo_cool_foreground, R.color.control_enabled_thermo_cool_background), DeviceTypes.TYPE_LIGHT - to Pair(R.color.light_foreground, R.color.control_enabled_light_background) + to Pair(R.color.light_foreground, R.color.control_enabled_light_background), + DeviceTypes.TYPE_CAMERA + to Pair(R.color.camera_foreground, R.color.control_enabled_default_background) ).withDefault { Pair(R.color.control_foreground, R.color.control_enabled_default_background) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt new file mode 100644 index 0000000000000..c2168aa8d9d9b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -0,0 +1,101 @@ +/* + * 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 + +import android.graphics.BlendMode +import android.graphics.BlendModeColorFilter +import android.graphics.drawable.ClipDrawable +import android.graphics.drawable.LayerDrawable +import android.view.View +import android.service.controls.Control +import android.service.controls.templates.ThumbnailTemplate +import android.util.TypedValue + +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 + +/** + * Supports display of static images on the background of the tile. When marked active, the title + * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only. + */ +class ThumbnailBehavior : Behavior { + lateinit var template: ThumbnailTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + private var shadowOffsetX: Float = 0f + private var shadowOffsetY: Float = 0f + private var shadowRadius: Float = 0f + private var shadowColor: Int = 0 + + private val enabled: Boolean + get() = template.isActive() + + override fun initialize(cvh: ControlViewHolder) { + this.cvh = cvh + + val outValue = TypedValue() + cvh.context.resources.getValue(R.dimen.controls_thumbnail_shadow_x, outValue, true) + shadowOffsetX = outValue.getFloat() + + cvh.context.resources.getValue(R.dimen.controls_thumbnail_shadow_y, outValue, true) + shadowOffsetY = outValue.getFloat() + + cvh.context.resources.getValue(R.dimen.controls_thumbnail_shadow_radius, outValue, true) + shadowRadius = outValue.getFloat() + + shadowColor = cvh.context.resources.getColor(R.color.control_thumbnail_shadow_color) + cvh.layout.setOnClickListener(View.OnClickListener() { + cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control) + }) + } + + override fun bind(cws: ControlWithState, colorOffset: Int) { + this.control = cws.control!! + cvh.setStatusText(control.getStatusText()) + template = control.getControlTemplate() as ThumbnailTemplate + + val ld = cvh.layout.getBackground() as LayerDrawable + val clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable + + clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL) + + if (template.isActive()) { + cvh.title.visibility = View.INVISIBLE + cvh.subtitle.visibility = View.INVISIBLE + cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor) + + cvh.bgExecutor.execute { + val drawable = template.getThumbnail().loadDrawable(cvh.context) + cvh.uiExecutor.execute { + val radius = cvh.context.getResources() + .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat() + clipLayer.setDrawable(CornerDrawable(drawable, radius)) + clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources + .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY)) + cvh.applyRenderInfo(enabled, colorOffset) + } + } + } else { + cvh.title.visibility = View.VISIBLE + cvh.subtitle.visibility = View.VISIBLE + cvh.status.setShadowLayer(0f, 0f, 0f, shadowColor) + } + + cvh.applyRenderInfo(enabled, colorOffset) + } +}