Merge "Animate ranges and toggles" into rvc-dev am: 75c2e16120 am: 8bddaff99f

Change-Id: I595d6f2e5ed53a42f1038bfb2861bdb276027a1c
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 6923079..2eba952 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -54,6 +54,11 @@
     public static final Interpolator PANEL_CLOSE_ACCELERATED
             = new PathInterpolator(0.3f, 0, 0.5f, 1);
     public static final Interpolator BOUNCE = new BounceInterpolator();
+    /**
+     * For state transitions on the control panel that lives in GlobalActions.
+     */
+    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+            1.0f);
 
     /**
      * Interpolator to be used when animating a move based on a click. Pair with enough duration.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index ad86eeb..2c1a91d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -24,12 +24,11 @@
 import android.service.controls.actions.CommandAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
-
 import com.android.systemui.R
 
 object ControlActionCoordinator {
-    public const val MIN_LEVEL = 0
-    public const val MAX_LEVEL = 10000
+    const val MIN_LEVEL = 0
+    const val MAX_LEVEL = 10000
 
     private var dialog: Dialog? = null
 
@@ -40,9 +39,6 @@
 
     fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
         cvh.action(BooleanAction(templateId, !isChecked))
-
-        val nextLevel = if (isChecked) MIN_LEVEL else MAX_LEVEL
-        cvh.clipLayer.setLevel(nextLevel)
     }
 
     fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
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 0eb6cb1..055adc6f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.controls.ui
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.drawable.ClipDrawable
 import android.graphics.drawable.GradientDrawable
@@ -32,11 +35,11 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
-
+import com.android.internal.graphics.ColorUtils
+import com.android.systemui.Interpolators
+import com.android.systemui.R
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.R
-
 import kotlin.reflect.KClass
 
 /**
@@ -53,15 +56,17 @@
 ) {
 
     companion object {
+        const val STATE_ANIMATION_DURATION = 700L
         private const val UPDATE_DELAY_IN_MILLIS = 3000L
         private const val ALPHA_ENABLED = (255.0 * 0.2).toInt()
-        private const val ALPHA_DISABLED = 255
+        private const val ALPHA_DISABLED = 0
         private val FORCE_PANEL_DEVICES = setOf(
             DeviceTypes.TYPE_THERMOSTAT,
             DeviceTypes.TYPE_CAMERA
         )
     }
 
+    private var stateAnimator: ValueAnimator? = null
     val icon: ImageView = layout.requireViewById(R.id.icon)
     val status: TextView = layout.requireViewById(R.id.status)
     val title: TextView = layout.requireViewById(R.id.title)
@@ -79,6 +84,7 @@
         val ld = layout.getBackground() as LayerDrawable
         ld.mutate()
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
+        clipLayer.alpha = ALPHA_DISABLED
         // needed for marquee to start
         status.setSelected(true)
     }
@@ -160,30 +166,49 @@
         }
     }
 
-    internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) {
+    internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0, animated: Boolean = true) {
         setEnabled(enabled)
 
         val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
 
         val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
-        val (bg, alpha) = if (enabled) {
+        val (bg, newAlpha) = if (enabled) {
             Pair(ri.enabledBackground, ALPHA_ENABLED)
         } else {
             Pair(R.color.control_default_background, ALPHA_DISABLED)
         }
 
         status.setTextColor(fg)
-
         icon.setImageDrawable(ri.icon)
 
         // do not color app icons
         if (deviceType != DeviceTypes.TYPE_ROUTINE) {
-            icon.setImageTintList(fg)
+            icon.imageTintList = fg
         }
 
         (clipLayer.getDrawable() as GradientDrawable).apply {
-            setColor(context.getResources().getColor(bg, context.getTheme()))
-            setAlpha(alpha)
+            val newColor = context.resources.getColor(bg, context.theme)
+            stateAnimator?.cancel()
+            if (animated) {
+                val oldColor = color?.defaultColor ?: newColor
+                stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply {
+                    addUpdateListener {
+                        alpha = it.animatedValue as Int
+                        setColor(ColorUtils.blendARGB(oldColor, newColor, it.animatedFraction))
+                    }
+                    addListener(object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            stateAnimator = null
+                        }
+                    })
+                    duration = STATE_ANIMATION_DURATION
+                    interpolator = Interpolators.CONTROL_STATE
+                    start()
+                }
+            } else {
+                alpha = newAlpha
+                setColor(newColor)
+            }
         }
     }
 
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 a3368ef..368d139 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -18,12 +18,10 @@
 
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
-import android.view.View
 import android.service.controls.Control
 import android.service.controls.templates.ToggleTemplate
-
+import android.view.View
 import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
 import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
 
 class ToggleBehavior : Behavior {
@@ -34,7 +32,7 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        cvh.applyRenderInfo(false)
+        cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
             ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
@@ -49,9 +47,9 @@
 
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
+        clipLayer.level = MAX_LEVEL
 
         val checked = template.isChecked()
-        clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL)
         cvh.applyRenderInfo(checked)
     }
 }
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 e8785e1..d8b26e2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -16,11 +16,20 @@
 
 package com.android.systemui.controls.ui
 
-import android.os.Bundle
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.drawable.Drawable
 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.RangeTemplate
+import android.service.controls.templates.ToggleRangeTemplate
 import android.util.Log
+import android.util.MathUtils
+import android.util.TypedValue
 import android.view.GestureDetector
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
@@ -29,19 +38,14 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
-import android.service.controls.Control
-import android.service.controls.actions.FloatAction
-import android.service.controls.templates.RangeTemplate
-import android.service.controls.templates.ToggleRangeTemplate
-import android.util.TypedValue
-
+import com.android.systemui.Interpolators
 import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
 import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
-
+import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
 import java.util.IllegalFormatException
 
 class ToggleRangeBehavior : Behavior {
+    private var rangeAnimator: ValueAnimator? = null
     lateinit var clipLayer: Drawable
     lateinit var template: ToggleRangeTemplate
     lateinit var control: Control
@@ -61,20 +65,21 @@
         status = cvh.status
         context = status.getContext()
 
-        cvh.applyRenderInfo(false)
+        cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
 
         val gestureListener = ToggleRangeGestureListener(cvh.layout)
         val gestureDetector = GestureDetector(context, gestureListener)
         cvh.layout.setOnTouchListener { v: View, e: MotionEvent ->
             if (gestureDetector.onTouchEvent(e)) {
-                return@setOnTouchListener true
+                // Don't return true to let the state list change to "pressed"
+                return@setOnTouchListener false
             }
 
             if (e.getAction() == MotionEvent.ACTION_UP && gestureListener.isDragging) {
                 v.getParent().requestDisallowInterceptTouchEvent(false)
                 gestureListener.isDragging = false
                 endUpdateRange()
-                return@setOnTouchListener true
+                return@setOnTouchListener false
             }
 
             return@setOnTouchListener false
@@ -87,17 +92,18 @@
         currentStatusText = control.getStatusText()
         status.setText(currentStatusText)
 
+        // ControlViewHolder sets a long click listener, but we want to handle touch in
+        // here instead, otherwise we'll have state conflicts.
+        cvh.layout.setOnLongClickListener(null)
+
         val ld = cvh.layout.getBackground() as LayerDrawable
         clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
-        clipLayer.setLevel(MIN_LEVEL)
 
         template = control.getControlTemplate() as ToggleRangeTemplate
         rangeTemplate = template.getRange()
 
         val checked = template.isChecked()
-        val currentRatio = rangeTemplate.getCurrentValue() /
-                (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())
-        updateRange(currentRatio, checked, /* isDragging */ false)
+        updateRange(rangeToLevelValue(rangeTemplate.currentValue), checked, /* isDragging */ false)
 
         cvh.applyRenderInfo(checked)
 
@@ -146,9 +152,8 @@
                         } else {
                             val value = arguments.getFloat(
                                 AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)
-                            val ratioDiff = (value - rangeTemplate.getCurrentValue()) /
-                                (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())
-                            updateRange(ratioDiff, template.isChecked(), /* isDragging */ false)
+                            val level = rangeToLevelValue(value - rangeTemplate.getCurrentValue())
+                            updateRange(level, template.isChecked(), /* isDragging */ false)
                             endUpdateRange()
                             true
                         }
@@ -172,13 +177,30 @@
                 .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
     }
 
-    fun updateRange(ratioDiff: Float, checked: Boolean, isDragging: Boolean) {
-        val changeAmount = if (checked) (MAX_LEVEL * ratioDiff).toInt() else MIN_LEVEL
-        val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, clipLayer.getLevel() + changeAmount))
-        clipLayer.setLevel(newLevel)
+    fun updateRange(level: Int, checked: Boolean, isDragging: Boolean) {
+        val newLevel = if (checked) Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level)) else MIN_LEVEL
+
+        rangeAnimator?.cancel()
+        if (isDragging) {
+            clipLayer.level = newLevel
+        } else {
+            rangeAnimator = ValueAnimator.ofInt(cvh.clipLayer.level, newLevel).apply {
+                addUpdateListener {
+                    cvh.clipLayer.level = it.animatedValue as Int
+                }
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        rangeAnimator = null
+                    }
+                })
+                duration = ControlViewHolder.STATE_ANIMATION_DURATION
+                interpolator = Interpolators.CONTROL_STATE
+                start()
+            }
+        }
 
         if (checked) {
-            val newValue = levelToRangeValue(clipLayer.getLevel())
+            val newValue = levelToRangeValue(newLevel)
             currentRangeValue = format(rangeTemplate.getFormatString().toString(),
                     DEFAULT_FORMAT, newValue)
             val text = if (isDragging) {
@@ -206,9 +228,13 @@
     }
 
     private fun levelToRangeValue(i: Int): Float {
-        val ratio = i.toFloat() / MAX_LEVEL
-        return rangeTemplate.getMinValue() +
-            (ratio * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()))
+        return MathUtils.constrainedMap(rangeTemplate.minValue, rangeTemplate.maxValue,
+                MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(), i.toFloat())
+    }
+
+    private fun rangeToLevelValue(i: Float): Int {
+        return MathUtils.constrainedMap(MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(),
+                rangeTemplate.minValue, rangeTemplate.maxValue, i).toInt()
     }
 
     fun endUpdateRange() {
@@ -247,6 +273,9 @@
         }
 
         override fun onLongPress(e: MotionEvent) {
+            if (isDragging) {
+                return
+            }
             ControlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
         }
 
@@ -265,8 +294,10 @@
                 isDragging = true
             }
 
-            this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(),
-                /* checked */ true, /* 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)
             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 b02c9c8..fd96cea 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -37,7 +37,7 @@
 
     override fun initialize(cvh: ControlViewHolder) {
         this.cvh = cvh
-        cvh.applyRenderInfo(false)
+        cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
 
         cvh.layout.setOnClickListener(View.OnClickListener() {
             ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)