Home | History | Annotate | Download | only in controllers
      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 
    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 
    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      */
    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      */
    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 
    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 
    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 
    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 
    513     public void toggleHvacUi() {
    514         if(mCurrentState != STATE_COLLAPSED) {
    515             mCollapseHvac.onClick(null);
    516         } else {
    517             mExpandHvac.onClick(null);
    518         }
    519     }
    520 
    521     public View.OnClickListener mExpandHvac = new View.OnClickListener() {
    522         @Override
    523         public void onClick(View v) {
    524              if (mInAnimation) {
    525                 return;
    526             }
    527             if (mCurrentState != STATE_FULL_EXPANDED) {
    528                 transitionState(mCurrentState, STATE_FULL_EXPANDED);
    529             }
    530         }
    531     };
    532 
    533 
    534     private ValueAnimator.AnimatorUpdateListener mHeightUpdateListener
    535             = new ValueAnimator.AnimatorUpdateListener() {
    536         @Override
    537         public void onAnimationUpdate(ValueAnimator animation) {
    538             int height = (Integer) animation.getAnimatedValue();
    539             int currentHeight = mPanel.getLayoutParams().height;
    540             mPanel.getLayoutParams().height = height;
    541             mPanel.setTop(mPanel.getTop() + height - currentHeight);
    542             mPanel.requestLayout();
    543         }
    544     };
    545 
    546     /**
    547      * Handles the necessary setup/clean up before and after a state transition.
    548      */
    549     private class HvacPanelStateTransition {
    550         private @HvacPanelState int mEndState;
    551         private @HvacPanelState int mStartState;
    552 
    553         public HvacPanelStateTransition(@HvacPanelState int startState,
    554                 @HvacPanelState int endState) {
    555             mStartState = startState;
    556             mEndState = endState;
    557         }
    558 
    559         public void onTransitionStart() {
    560             mInAnimation = true;
    561             if (mEndState == STATE_FULL_EXPANDED) {
    562                 mPassengerTemperatureBarExpanded.setVisibility(View.VISIBLE);
    563                 mDriverTemperatureBarExpanded.setVisibility(View.VISIBLE);
    564                 if (mShowCollapsed) {
    565                     mDriverTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
    566                     mPassengerTemperatureBarCollapsed.setVisibility(View.INVISIBLE);
    567                 }
    568                 mContainer.setAlpha(1);
    569                 mContainer.setVisibility(View.VISIBLE);
    570             }
    571         }
    572 
    573         public void onTransitionComplete() {
    574             if (mEndState == STATE_COLLAPSED) {
    575                 if (mShowCollapsed) {
    576                     mDriverTemperatureBarCollapsed.setVisibility(View.VISIBLE);
    577                     mPassengerTemperatureBarCollapsed.setVisibility(View.VISIBLE);
    578                 }
    579                 handler.postAtFrontOfQueue(() -> {
    580                     mDriverTemperatureBarExpanded.setVisibility(View.INVISIBLE);
    581                     mPassengerTemperatureBarExpanded.setVisibility(View.INVISIBLE);
    582                 });
    583             }
    584             if (mStartState == STATE_FULL_EXPANDED) {
    585                 mContainer.setVisibility(View.INVISIBLE);
    586             }
    587 
    588             // set new states
    589             mCurrentState = mEndState;
    590             mInAnimation = false;
    591         }
    592     }
    593 }
    594