• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016, 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 package com.android.car.hvac.controllers;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorSet;
20 import android.animation.ObjectAnimator;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.drawable.Drawable;
25 import android.os.Handler;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.WindowManager;
29 import android.widget.ImageView;
30 
31 import androidx.annotation.IntDef;
32 
33 import com.android.car.hvac.HvacController;
34 import com.android.car.hvac.R;
35 import com.android.car.hvac.ui.FanDirectionButtons;
36 import com.android.car.hvac.ui.FanSpeedBar;
37 import com.android.car.hvac.ui.HvacPanelRow;
38 import com.android.car.hvac.ui.SeatWarmerButton;
39 import com.android.car.hvac.ui.TemperatureBarOverlay;
40 import com.android.car.hvac.ui.ToggleButton;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * A state machine to control transition from various HVAC UI layouts.
47  */
48 public class HvacPanelController {
49     private static final int PANEL_ANIMATION_TIME_MS = 200;
50     private static final int PANEL_COLLAPSE_ANIMATION_TIME_MS = 500;
51 
52     private static final int PANEL_ANIMATION_DELAY_MS = 100;
53     private static final int PANEL_ANIMATION_LONG_DELAY_MS = 3 * PANEL_ANIMATION_DELAY_MS;
54 
55     private static final float DISABLED_BUTTON_ALPHA = 0.20f;
56     private static final float ENABLED_BUTTON_ALPHA = 1.0f;
57 
58     private static final int STATE_COLLAPSED = 0;
59     private static final int STATE_COLLAPSED_DIMMED = 1;
60     private static final int STATE_FULL_EXPANDED = 2;
61     // Allows for delayed invocation of code. Thus we can control UI events to happen after
62     // others. Example: set something visible but do it after we've complete current UI updates.
63     private static Handler handler = new Handler();
64 
65     // We have both a collapsed and expanded version of the overlays due to a bug
66     // that does not correctly rendering a window resize event. Thus we toggle the the visibility
67     // of the windows instead. A better solution would be a having separate views collapsed state
68     // since the it does not need the other elements but this works for now.
69     private TemperatureBarOverlay mDriverTemperatureBarCollapsed;
70     private TemperatureBarOverlay mPassengerTemperatureBarCollapsed;
71     private TemperatureBarOverlay mDriverTemperatureBarExpanded;
72     private TemperatureBarOverlay mPassengerTemperatureBarExpanded;
73     private final boolean mShowCollapsed;
74 
75     @IntDef({STATE_COLLAPSED,
76             STATE_COLLAPSED_DIMMED,
77             STATE_FULL_EXPANDED})
78     private @interface HvacPanelState {}
79 
80     private @HvacPanelState int mCurrentState;
81 
82     private int mPanelCollapsedHeight;
83     private int mPanelFullExpandedHeight;
84 
85     private View mPanel;
86     private View mContainer;
87 
88     private SeatWarmerButton mDriverSeatWarmer;
89     private SeatWarmerButton mPassengerSeatWarmer;
90 
91     private ToggleButton mHvacPowerSwitch;
92 
93     private ToggleButton mAcButton;
94     private ToggleButton mRecycleAirButton;
95 
96     private ToggleButton mFrontDefrosterButton;
97     private ToggleButton mRearDefrosterButton;
98 
99     private Drawable mAutoOnDrawable;
100     private Drawable mAutoOffDrawable;
101 
102     private ImageView mAutoButton;
103 
104     private HvacPanelRow mPanelTopRow;
105     private HvacPanelRow mPanelBottomRow;
106 
107     private FanSpeedBar mFanSpeedBar;
108     private FanDirectionButtons mFanDirectionButtons;
109 
110     private float mTopPanelMaxAlpha = 1.0f;
111 
112     private WindowManager mWindowManager;
113 
114     private HvacPanelStateTransition mTransition;
115 
116     private View mHvacFanControlBackground;
117 
118     private HvacController mHvacController;
119     private FanSpeedBarController mFanSpeedBarController;
120     private FanDirectionButtonsController mFanDirectionButtonsController;
121     private TemperatureController mTemperatureController;
122     private TemperatureController mTemperatureControllerCollapsed;
123     private SeatWarmerController mSeatWarmerController;
124 
125     private boolean mInAnimation;
126 
127     // TODO: read from shared pref
128     private boolean mAutoMode;
129 
HvacPanelController(Context context, View container, WindowManager windowManager, TemperatureBarOverlay driverTemperatureExpanded, TemperatureBarOverlay passengerTemperatureExpanded, TemperatureBarOverlay driverTemperatureBarCollapsed, TemperatureBarOverlay passengerTemperatureBarCollapsed)130     public HvacPanelController(Context context, View container,
131             WindowManager windowManager,
132             TemperatureBarOverlay driverTemperatureExpanded,
133             TemperatureBarOverlay passengerTemperatureExpanded,
134             TemperatureBarOverlay driverTemperatureBarCollapsed,
135             TemperatureBarOverlay passengerTemperatureBarCollapsed) {
136         Resources res = context.getResources();
137         mShowCollapsed = res.getBoolean(R.bool.config_showCollapsedBars);
138 
139         mDriverTemperatureBarCollapsed = driverTemperatureBarCollapsed;
140         mPassengerTemperatureBarCollapsed = passengerTemperatureBarCollapsed;
141 
142         mCurrentState = STATE_COLLAPSED;
143         mWindowManager = windowManager;
144 
145         mPanelCollapsedHeight = res.getDimensionPixelSize(R.dimen.car_hvac_panel_collapsed_height);
146         mPanelFullExpandedHeight
147                 = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height);
148 
149         mAutoOffDrawable = res.getDrawable(R.drawable.ic_auto_off);
150         mAutoOnDrawable = res.getDrawable(R.drawable.ic_auto_on);
151 
152         mDriverTemperatureBarExpanded = driverTemperatureExpanded;
153         mPassengerTemperatureBarExpanded = passengerTemperatureExpanded;
154 
155         mDriverTemperatureBarExpanded.setCloseButtonOnClickListener(mCollapseHvac);
156         mPassengerTemperatureBarExpanded.setCloseButtonOnClickListener(mCollapseHvac);
157 
158         // Initially the hvac panel is collapsed, hide the expanded version.
159         mDriverTemperatureBarExpanded.setVisibility(View.INVISIBLE);
160         mPassengerTemperatureBarExpanded.setVisibility(View.INVISIBLE);
161 
162         mPassengerTemperatureBarCollapsed.setBarOnClickListener(mExpandHvac);
163         mDriverTemperatureBarCollapsed.setBarOnClickListener(mExpandHvac);
164 
165         mContainer = container;
166         mContainer.setVisibility(View.INVISIBLE);
167         mContainer.setOnClickListener(mCollapseHvac);
168         mPanel = mContainer.findViewById(R.id.hvac_center_panel);
169 
170         mHvacFanControlBackground = mPanel.findViewById(R.id.fan_control_bg);
171         // set clickable so that clicks are not forward to the mContainer. This way a miss click
172         // does not close the UI
173         mPanel.setClickable(true);
174 
175         // Set up top row buttons
176         mPanelTopRow = (HvacPanelRow) mContainer.findViewById(R.id.top_row);
177 
178         mAcButton = (ToggleButton) mPanelTopRow.findViewById(R.id.ac_button);
179         mAcButton.setToggleIcons(res.getDrawable(R.drawable.ic_ac_on),
180                 res.getDrawable(R.drawable.ic_ac_off));
181 
182         mRecycleAirButton = (ToggleButton) mPanelTopRow.findViewById(R.id.recycle_air_button);
183 
184         mRecycleAirButton.setToggleIcons(res.getDrawable(R.drawable.ic_recycle_air_on),
185                 res.getDrawable(R.drawable.ic_recycle_air_off));
186 
187         // Setup bottom row buttons
188         mPanelBottomRow = (HvacPanelRow) mContainer.findViewById(R.id.bottom_row);
189 
190         mAutoButton = (ImageView) mContainer.findViewById(R.id.auto_button);
191         mAutoButton.setOnClickListener(mAutoButtonClickListener);
192 
193         mFrontDefrosterButton = (ToggleButton) mPanelBottomRow.findViewById(R.id.front_defroster);
194         mRearDefrosterButton = (ToggleButton) mPanelBottomRow.findViewById(R.id.rear_defroster);
195 
196         mFrontDefrosterButton.setToggleIcons(res.getDrawable(R.drawable.ic_front_defroster_on),
197                 res.getDrawable(R.drawable.ic_front_defroster_off));
198 
199         mRearDefrosterButton.setToggleIcons(res.getDrawable(R.drawable.ic_rear_defroster_on),
200                 res.getDrawable(R.drawable.ic_rear_defroster_off));
201 
202         mFanSpeedBar = (FanSpeedBar) mContainer.findViewById(R.id.fan_speed_bar);
203         mFanDirectionButtons = (FanDirectionButtons) mContainer.findViewById(R.id.fan_direction_buttons);
204 
205         mDriverSeatWarmer = (SeatWarmerButton) mContainer.findViewById(R.id.left_seat_heater);
206         mPassengerSeatWarmer = (SeatWarmerButton) mContainer.findViewById(R.id.right_seat_heater);
207 
208         mHvacPowerSwitch = (ToggleButton)mPanelBottomRow.findViewById(R.id.hvac_master_switch);
209         // TODO: this is not good UX design - just a placeholder
210         mHvacPowerSwitch.setToggleIcons(res.getDrawable(R.drawable.ac_master_switch_on),
211             res.getDrawable(R.drawable.ac_master_switch_off));
212 
213         if (!mShowCollapsed) {
214             mDriverTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
215             mPassengerTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
216         }
217     }
218 
updateHvacController(HvacController controller)219     public void updateHvacController(HvacController controller) {
220         //TODO: handle disconnected HvacController.
221         mHvacController = controller;
222 
223         mFanSpeedBarController = new FanSpeedBarController(mFanSpeedBar, mHvacController);
224         mFanDirectionButtonsController
225                 = new FanDirectionButtonsController(mFanDirectionButtons, mHvacController);
226         mTemperatureController = new TemperatureController(
227                 mPassengerTemperatureBarExpanded,
228                 mDriverTemperatureBarExpanded,
229                 mPassengerTemperatureBarCollapsed,
230                 mDriverTemperatureBarCollapsed,
231                 mHvacController);
232         mSeatWarmerController = new SeatWarmerController(mPassengerSeatWarmer,
233                 mDriverSeatWarmer, mHvacController);
234 
235         // Toggle buttons do not need additional logic to map between hardware
236         // and UI settings. Simply use a ToggleListener to handle clicks.
237         mAcButton.setIsOn(mHvacController.getAcState());
238         mAcButton.setToggleListener(new ToggleButton.ToggleListener() {
239             @Override
240             public void onToggled(boolean isOn) {
241                 mHvacController.setAcState(isOn);
242             }
243         });
244 
245         mFrontDefrosterButton.setIsOn(mHvacController.getFrontDefrosterState());
246         mFrontDefrosterButton.setToggleListener(new ToggleButton.ToggleListener() {
247             @Override
248             public void onToggled(boolean isOn) {
249                 mHvacController.setFrontDefrosterState(isOn);
250             }
251         });
252 
253         mRearDefrosterButton.setIsOn(mHvacController.getRearDefrosterState());
254         mRearDefrosterButton.setToggleListener(new ToggleButton.ToggleListener() {
255             @Override
256             public void onToggled(boolean isOn) {
257                 mHvacController.setRearDefrosterState(isOn);
258             }
259         });
260 
261         mRecycleAirButton.setIsOn(mHvacController.getAirCirculationState());
262         mRecycleAirButton.setToggleListener(new ToggleButton.ToggleListener() {
263             @Override
264             public void onToggled(boolean isOn) {
265                 mHvacController.setAirCirculation(isOn);
266             }
267         });
268 
269         setAutoMode(mHvacController.getAutoModeState());
270 
271         mHvacPowerSwitch.setIsOn(mHvacController.getHvacPowerState());
272         mHvacPowerSwitch.setToggleListener(isOn -> mHvacController.setHvacPowerState(isOn));
273 
274         mHvacController.registerCallback(mToggleButtonCallbacks);
275         mToggleButtonCallbacks.onHvacPowerChange(mHvacController.getHvacPowerState());
276     }
277 
278     private HvacController.Callback mToggleButtonCallbacks
279             = new HvacController.Callback() {
280         @Override
281         public void onAirCirculationChange(boolean isOn) {
282             mRecycleAirButton.setIsOn(isOn);
283         }
284 
285         @Override
286         public void onFrontDefrosterChange(boolean isOn) {
287             mFrontDefrosterButton.setIsOn(isOn);
288         }
289 
290         @Override
291         public void onRearDefrosterChange(boolean isOn) {
292             mRearDefrosterButton.setIsOn(isOn);
293         }
294 
295         @Override
296         public void onAcStateChange(boolean isOn) {
297             mAcButton.setIsOn(isOn);
298         }
299 
300         @Override
301         public void onAutoModeChange(boolean isOn) {
302             mAutoMode = isOn;
303             setAutoMode(mAutoMode);
304         }
305     };
306 
307     /**
308      * Take the listeners and animators from a {@link AnimatorSet} and merge them to the
309      * input {@link Animator} and {@link android.animation.Animator.AnimatorListener} lists.
310      */
combineAnimationSet(List<Animator> animatorList, List<Animator.AnimatorListener> listenerList, AnimatorSet set)311     private void combineAnimationSet(List<Animator> animatorList,
312             List<Animator.AnimatorListener> listenerList, AnimatorSet set) {
313 
314         ArrayList<Animator> list = set.getChildAnimations();
315         if (list != null) {
316             int size = list.size();
317             for (int i = 0; i < size; i++) {
318                 animatorList.add(list.get(i));
319             }
320         }
321 
322         ArrayList<Animator.AnimatorListener> listeners = set.getListeners();
323         if (listeners != null) {
324             int size = listeners.size();
325             for (int i = 0; i < size; i++) {
326                 listenerList.add(listeners.get(i));
327             }
328         }
329     }
330 
331     /**
332      * Play necessary animations between {@link HvacPanelState} transitions
333      */
transitionState(@vacPanelState int startState, @HvacPanelState int endState)334     private void transitionState(@HvacPanelState int startState, @HvacPanelState int endState) {
335         if (startState == endState || mInAnimation) {
336             return;
337         }
338 
339         List<Animator> animationList = new ArrayList<>();
340         List<Animator.AnimatorListener> listenerList = new ArrayList<>();
341         ValueAnimator heightAnimator = getPanelHeightAnimator(startState, endState);
342         mTransition = new HvacPanelStateTransition(startState, endState);
343         ValueAnimator fanBgAlphaAnimator;
344         switch (endState) {
345             case STATE_COLLAPSED:
346                 // Transition to collapsed state:
347                 // 1. Collapse the temperature bars.
348                 // 2. Collapse the top and bottom panel, staggered with a different delay.
349                 // 3. Decrease height of the hvac center panel, but maintain container height.
350                 // 4. Fade the background of the fan controls seperately to create staggered effect.
351                 animationList.add(heightAnimator);
352                 heightAnimator.setDuration(PANEL_COLLAPSE_ANIMATION_TIME_MS);
353                 fanBgAlphaAnimator
354                         = ObjectAnimator.ofFloat(mHvacFanControlBackground, View.ALPHA, 1, 0)
355                         .setDuration(PANEL_COLLAPSE_ANIMATION_TIME_MS);
356                 fanBgAlphaAnimator.setStartDelay(PANEL_ANIMATION_DELAY_MS);
357                 animationList.add(fanBgAlphaAnimator);
358 
359                 ValueAnimator panelAlphaAnimator
360                         = ObjectAnimator.ofFloat(mContainer, View.ALPHA, 1, 0);
361                 panelAlphaAnimator.setDuration(200);
362                 panelAlphaAnimator.setStartDelay(300);
363 
364                 animationList.add(panelAlphaAnimator);
365 
366                 combineAnimationSet(animationList, listenerList,
367                         mDriverTemperatureBarExpanded.getCollapseAnimations());
368                 combineAnimationSet(animationList, listenerList,
369                         mPassengerTemperatureBarExpanded.getCollapseAnimations());
370 
371                 combineAnimationSet(animationList, listenerList,
372                         mPanelTopRow.getCollapseAnimation(PANEL_ANIMATION_DELAY_MS,
373                                 mTopPanelMaxAlpha));
374                 combineAnimationSet(animationList, listenerList,
375                         mPanelBottomRow.getCollapseAnimation(PANEL_ANIMATION_DELAY_MS,
376                                 mTopPanelMaxAlpha));
377                 break;
378             case STATE_COLLAPSED_DIMMED:
379                 // Hide the temperature numbers, open arrows and auto state button.
380                 // TODO: determine if this section is still needed.
381                 break;
382             case STATE_FULL_EXPANDED:
383                 // Transition to expaneded state:
384                 // 1. Expand the temperature bars.
385                 // 2. Expand the top and bottom panel, staggered with a different delay.
386                 // 3. Increase height of the hvac center panel, but maintain container height.
387                 // 4. Fade in fan control background in a staggered manner.
388                 fanBgAlphaAnimator
389                         = ObjectAnimator.ofFloat(mHvacFanControlBackground, View.ALPHA, 0, 1)
390                         .setDuration(PANEL_ANIMATION_TIME_MS);
391                 fanBgAlphaAnimator.setStartDelay(PANEL_ANIMATION_DELAY_MS);
392                 animationList.add(fanBgAlphaAnimator);
393 
394                 animationList.add(heightAnimator);
395                 combineAnimationSet(animationList, listenerList,
396                         mDriverTemperatureBarExpanded.getExpandAnimatons());
397                 combineAnimationSet(animationList, listenerList,
398                         mPassengerTemperatureBarExpanded.getExpandAnimatons());
399 
400                 // During expansion, the bottom panel animation should be delayed
401                 combineAnimationSet(animationList, listenerList,
402                         mPanelTopRow.getExpandAnimation(PANEL_ANIMATION_DELAY_MS,
403                                 mTopPanelMaxAlpha));
404                 combineAnimationSet(animationList, listenerList,
405                         mPanelBottomRow.getExpandAnimation(PANEL_ANIMATION_LONG_DELAY_MS, 1f));
406                 break;
407             default:
408         }
409 
410         // If there are animations for the state change, play them all together and ensure
411         // the animation listeners are attached.
412         if (animationList.size() > 0) {
413             AnimatorSet animatorSet = new AnimatorSet();
414             animatorSet.playTogether(animationList);
415             for (Animator.AnimatorListener listener : listenerList) {
416                 animatorSet.addListener(listener);
417             }
418             animatorSet.addListener(mAnimatorListener);
419             animatorSet.start();
420         }
421     }
422 
423     private AnimatorSet.AnimatorListener mAnimatorListener = new AnimatorSet.AnimatorListener() {
424         @Override
425         public void onAnimationStart(Animator animation) {
426             mTransition.onTransitionStart();
427         }
428 
429         @Override
430         public void onAnimationEnd(Animator animation) {
431             mTransition.onTransitionComplete();
432         }
433 
434         @Override
435         public void onAnimationCancel(Animator animation) {}
436 
437         @Override
438         public void onAnimationRepeat(Animator animation) {}
439     };
440 
getPanelHeightAnimator(@vacPanelState int startState, @HvacPanelState int endState)441     private ValueAnimator getPanelHeightAnimator(@HvacPanelState int startState,
442             @HvacPanelState int endState) {
443         int startHeight = getStateHeight(startState);
444         int endHeight = getStateHeight(endState);
445         if (startHeight == endHeight) {
446             return null;
447         }
448 
449         ValueAnimator heightAnimator = new ValueAnimator().ofInt(startHeight, endHeight)
450                 .setDuration(PANEL_ANIMATION_TIME_MS);
451         heightAnimator.addUpdateListener(mHeightUpdateListener);
452         return heightAnimator;
453     }
454 
getStateHeight(@vacPanelState int state)455     private int getStateHeight(@HvacPanelState int state) {
456         switch (state) {
457             case STATE_COLLAPSED:
458             case STATE_COLLAPSED_DIMMED:
459                 return mPanelCollapsedHeight;
460             case STATE_FULL_EXPANDED:
461                 return mPanelFullExpandedHeight;
462             default:
463                 throw new IllegalArgumentException("No height mapped to HVAC State: " + state);
464         }
465     }
466 
setAutoMode(boolean isOn)467     private void setAutoMode(boolean isOn) {
468         if (isOn) {
469             mPanelTopRow.setOnTouchListener(new View.OnTouchListener() {
470                 @Override
471                 public boolean onTouch(View v, MotionEvent event) {
472                     return true;
473                 }
474             });
475             mAutoMode = true;
476             mPanelTopRow.disablePanel(true);
477             mTopPanelMaxAlpha = DISABLED_BUTTON_ALPHA;
478             mAutoButton.setImageDrawable(mAutoOnDrawable);
479         } else {
480             mPanelTopRow.disablePanel(false);
481             mTopPanelMaxAlpha = ENABLED_BUTTON_ALPHA;
482             mAutoButton.setImageDrawable(mAutoOffDrawable);
483         }
484         mHvacFanControlBackground.setAlpha(mTopPanelMaxAlpha);
485         mPanelTopRow.setAlpha(mTopPanelMaxAlpha);
486     }
487 
488     private View.OnClickListener mAutoButtonClickListener = new View.OnClickListener() {
489         @Override
490         public void onClick(View v) {
491             if (mAutoMode) {
492                 mAutoMode = false;
493             } else {
494                 mAutoMode = true;
495             }
496             mHvacController.setAutoMode(mAutoMode);
497             setAutoMode(mAutoMode);
498         }
499     };
500 
501      private View.OnClickListener mCollapseHvac = new View.OnClickListener() {
502          @Override
503          public void onClick(View v) {
504              if (mInAnimation) {
505                  return;
506              }
507              if (mCurrentState != STATE_COLLAPSED) {
508                  transitionState(mCurrentState, STATE_COLLAPSED);
509              }
510          }
511      };
512 
toggleHvacUi()513     public void toggleHvacUi() {
514         if (mCurrentState != STATE_COLLAPSED) {
515             mCollapseHvac.onClick(null);
516         } else {
517             mExpandHvac.onClick(null);
518         }
519     }
520 
collapseHvacUi()521     public void collapseHvacUi() {
522         if (mCurrentState != STATE_COLLAPSED) {
523             mCollapseHvac.onClick(null);
524         }
525     }
526 
527     public View.OnClickListener mExpandHvac = new View.OnClickListener() {
528         @Override
529         public void onClick(View v) {
530              if (mInAnimation) {
531                 return;
532             }
533             if (mCurrentState != STATE_FULL_EXPANDED) {
534                 transitionState(mCurrentState, STATE_FULL_EXPANDED);
535             }
536         }
537     };
538 
539 
540     private ValueAnimator.AnimatorUpdateListener mHeightUpdateListener
541             = new ValueAnimator.AnimatorUpdateListener() {
542         @Override
543         public void onAnimationUpdate(ValueAnimator animation) {
544             int height = (Integer) animation.getAnimatedValue();
545             int currentHeight = mPanel.getLayoutParams().height;
546             mPanel.getLayoutParams().height = height;
547             mPanel.setTop(mPanel.getTop() + height - currentHeight);
548             mPanel.requestLayout();
549         }
550     };
551 
552     /**
553      * Handles the necessary setup/clean up before and after a state transition.
554      */
555     private class HvacPanelStateTransition {
556         private @HvacPanelState int mEndState;
557         private @HvacPanelState int mStartState;
558 
HvacPanelStateTransition(@vacPanelState int startState, @HvacPanelState int endState)559         public HvacPanelStateTransition(@HvacPanelState int startState,
560                 @HvacPanelState int endState) {
561             mStartState = startState;
562             mEndState = endState;
563         }
564 
onTransitionStart()565         public void onTransitionStart() {
566             mInAnimation = true;
567             if (mEndState == STATE_FULL_EXPANDED) {
568                 mPassengerTemperatureBarExpanded.setVisibility(View.VISIBLE);
569                 mDriverTemperatureBarExpanded.setVisibility(View.VISIBLE);
570                 if (mShowCollapsed) {
571                     mDriverTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
572                     mPassengerTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
573                 }
574                 mContainer.setAlpha(1);
575                 mContainer.setVisibility(View.VISIBLE);
576             }
577         }
578 
onTransitionComplete()579         public void onTransitionComplete() {
580             if (mEndState == STATE_COLLAPSED) {
581                 if (mShowCollapsed) {
582                     mDriverTemperatureBarCollapsed.setVisibility(View.VISIBLE);
583                     mPassengerTemperatureBarCollapsed.setVisibility(View.VISIBLE);
584                 }
585                 handler.postAtFrontOfQueue(() -> {
586                     mDriverTemperatureBarExpanded.setVisibility(View.INVISIBLE);
587                     mPassengerTemperatureBarExpanded.setVisibility(View.INVISIBLE);
588                 });
589             }
590             if (mStartState == STATE_FULL_EXPANDED) {
591                 mContainer.setVisibility(View.INVISIBLE);
592             }
593 
594             // set new states
595             mCurrentState = mEndState;
596             mInAnimation = false;
597         }
598     }
599 }
600