• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.car.hvac;
18 
19 import static com.android.systemui.car.hvac.HvacController.convertToFahrenheit;
20 
21 import android.animation.ObjectAnimator;
22 import android.annotation.SuppressLint;
23 import android.content.Context;
24 import android.content.res.TypedArray;
25 import android.graphics.Color;
26 import android.graphics.Rect;
27 import android.graphics.drawable.ColorDrawable;
28 import android.util.AttributeSet;
29 import android.util.Property;
30 import android.view.Gravity;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewTreeObserver;
34 import android.widget.FrameLayout;
35 import android.widget.ImageView;
36 import android.widget.TextSwitcher;
37 import android.widget.TextView;
38 
39 import com.android.systemui.R;
40 
41 /**
42  * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
43  * the XML.
44  * XML properties:
45  * hvacAreaId - Example: VehicleAreaSeat.SEAT_ROW_1_LEFT (1)
46  * hvacOrientation = Example: left
47  */
48 public class AnimatedTemperatureView extends FrameLayout implements TemperatureView {
49 
50     private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f;
51     private static final Property<ColorDrawable, Integer> COLOR_PROPERTY =
52             new Property<ColorDrawable, Integer>(Integer.class, "color") {
53 
54                 @Override
55                 public Integer get(ColorDrawable object) {
56                     return object.getColor();
57                 }
58 
59                 @Override
60                 public void set(ColorDrawable object, Integer value) {
61                     object.setColor(value);
62                 }
63             };
64 
isHorizontal(int gravity)65     static boolean isHorizontal(int gravity) {
66         return Gravity.isHorizontal(gravity)
67                 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL;
68     }
69 
70     @SuppressLint("RtlHardcoded")
isLeft(int gravity, int layoutDirection)71     static boolean isLeft(int gravity, int layoutDirection) {
72         return Gravity
73                 .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection)
74                 == Gravity.LEFT;
75     }
76 
isVertical(int gravity)77     static boolean isVertical(int gravity) {
78         return Gravity.isVertical(gravity)
79                 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL;
80     }
81 
isTop(int gravity)82     static boolean isTop(int gravity) {
83         return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP;
84     }
85 
86     private final int mAreaId;
87     private final int mPivotOffset;
88     private final int mGravity;
89     private final int mTextAppearanceRes;
90     private final int mMinEms;
91     private final Rect mPaddingRect;
92     private final float mMinValue;
93     private final float mMaxValue;
94 
95     private final ColorDrawable mBackgroundColor;
96 
97     private final TemperatureColorStore mColorStore = new TemperatureColorStore();
98     private final TemperatureBackgroundAnimator mBackgroundAnimator;
99     private final TemperatureTextAnimator mTextAnimator;
100     boolean mDisplayInFahrenheit = false;
101 
102     private HvacController mHvacController;
103 
AnimatedTemperatureView(Context context, AttributeSet attrs)104     public AnimatedTemperatureView(Context context, AttributeSet attrs) {
105         super(context, attrs);
106         TypedArray typedArray = context.obtainStyledAttributes(attrs,
107                 R.styleable.AnimatedTemperatureView);
108         mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1);
109         mPivotOffset =
110                 typedArray.getDimensionPixelOffset(
111                         R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0);
112         mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity,
113                 Gravity.START);
114         mTextAppearanceRes =
115                 typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance,
116                         0);
117         mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0);
118         mMinValue = getResources().getFloat(R.dimen.hvac_min_value_celsius);
119         mMaxValue = getResources().getFloat(R.dimen.hvac_max_value_celsius);
120 
121         mPaddingRect =
122                 new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
123         setPadding(0, 0, 0, 0);
124 
125         setClipChildren(false);
126         setClipToPadding(false);
127 
128         // init Views
129         TextSwitcher textSwitcher = new TextSwitcher(context);
130         textSwitcher.setFactory(this::generateTextView);
131         ImageView background = new ImageView(context);
132         mBackgroundColor = new ColorDrawable(Color.TRANSPARENT);
133         background.setImageDrawable(mBackgroundColor);
134         background.setVisibility(View.GONE);
135 
136         mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background);
137 
138         mTextAnimator = new TemperatureTextAnimator(this, textSwitcher,
139                 getResources().getString(R.string.hvac_temperature_format), mPivotOffset,
140                 getResources().getString(R.string.hvac_min_text),
141                 getResources().getString(R.string.hvac_max_text));
142 
143         addView(background, ViewGroup.LayoutParams.MATCH_PARENT,
144                 ViewGroup.LayoutParams.MATCH_PARENT);
145         addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT,
146                 ViewGroup.LayoutParams.MATCH_PARENT);
147 
148         typedArray.recycle();
149     }
150 
151 
generateTextView()152     private TextView generateTextView() {
153         TextView textView = new TextView(getContext());
154         textView.setTextAppearance(mTextAppearanceRes);
155         textView.setAllCaps(true);
156         textView.setMinEms(mMinEms);
157         textView.setGravity(mGravity);
158         textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right,
159                 mPaddingRect.bottom);
160         textView.getViewTreeObserver()
161                 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
162                     @Override
163                     public boolean onPreDraw() {
164                         if (isHorizontal(mGravity)) {
165                             if (isLeft(mGravity, getLayoutDirection())) {
166                                 textView.setPivotX(-mPivotOffset);
167                             } else {
168                                 textView.setPivotX(textView.getWidth() + mPivotOffset);
169                             }
170                         }
171                         textView.getViewTreeObserver().removeOnPreDrawListener(this);
172                         return true;
173                     }
174                 });
175         textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
176                 ViewGroup.LayoutParams.MATCH_PARENT));
177 
178         return textView;
179     }
180 
181     @Override
setHvacController(HvacController controller)182     public void setHvacController(HvacController controller) {
183         mHvacController = controller;
184     }
185 
186     /**
187      * Formats the float for display
188      *
189      * @param temp - The current temp or NaN
190      */
191     @Override
setTemp(float temp)192     public void setTemp(float temp) {
193         if (mDisplayInFahrenheit) {
194             temp = convertToFahrenheit(temp);
195         }
196         mTextAnimator.setTemp(temp);
197         if (Float.isNaN(temp)) {
198             mBackgroundAnimator.hideCircle();
199             return;
200         }
201         int color;
202         if (isMinValue(temp)) {
203             color = mColorStore.getMinColor();
204         } else if (isMaxValue(temp)) {
205             color = mColorStore.getMaxColor();
206         } else {
207             color = mColorStore.getColorForTemperature(temp);
208         }
209         if (mBackgroundAnimator.isOpen()) {
210             ObjectAnimator colorAnimator =
211                     ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color);
212             colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore
213                     .lerpColor(fraction, (int) startValue, (int) endValue));
214             colorAnimator.start();
215         } else {
216             mBackgroundColor.setColor(color);
217         }
218 
219         mBackgroundAnimator.animateOpen();
220     }
221 
222     @Override
setDisplayInFahrenheit(boolean displayInFahrenheit)223     public void setDisplayInFahrenheit(boolean displayInFahrenheit) {
224         mDisplayInFahrenheit = displayInFahrenheit;
225     }
226 
isMinValue(float temp)227     boolean isMinValue(float temp) {
228         return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue);
229     }
230 
isMaxValue(float temp)231     boolean isMaxValue(float temp) {
232         return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue);
233     }
234 
isApproxEqual(float left, float right)235     private boolean isApproxEqual(float left, float right) {
236         return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA;
237     }
238 
getGravity()239     int getGravity() {
240         return mGravity;
241     }
242 
getPivotOffset()243     int getPivotOffset() {
244         return mPivotOffset;
245     }
246 
getPaddingRect()247     Rect getPaddingRect() {
248         return mPaddingRect;
249     }
250 
251     /**
252      * @return hvac AreaId - Example: VehicleAreaSeat.SEAT_ROW_1_LEFT (1)
253      */
254     @Override
getAreaId()255     public int getAreaId() {
256         return mAreaId;
257     }
258 
259     @Override
onDetachedFromWindow()260     protected void onDetachedFromWindow() {
261         super.onDetachedFromWindow();
262         mBackgroundAnimator.stopAnimations();
263     }
264 }
265 
266