1 /*
2  * Copyright (C) 2023 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.car.carlauncher.calmmode;
18 
19 import android.app.Application;
20 import android.car.Car;
21 import android.car.VehicleAreaSeat;
22 import android.car.VehiclePropertyIds;
23 import android.car.VehicleUnit;
24 import android.car.hardware.CarPropertyValue;
25 import android.car.hardware.property.CarPropertyManager;
26 import android.icu.util.MeasureUnit;
27 import android.os.Build;
28 import android.util.Log;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.VisibleForTesting;
33 import androidx.lifecycle.AndroidViewModel;
34 import androidx.lifecycle.LiveData;
35 import androidx.lifecycle.MutableLiveData;
36 
37 
38 
39 public class TemperatureViewModel extends AndroidViewModel
40         implements CarPropertyManager.CarPropertyEventCallback {
41     private static final boolean DEBUG = Build.isDebuggable();
42     private static final String TAG = TemperatureViewModel.class.getSimpleName();
43     private Car mCar;
44     private CarPropertyManager mPropertyManager;
45     private MutableLiveData<TemperatureData> mTemperatureData = new MutableLiveData<>();
46 
47     private boolean mIsTemperatureSet;
48     private float mValue;
49     private MeasureUnit mUnit;
TemperatureViewModel(@onNull Application application)50     public TemperatureViewModel(@NonNull Application application) {
51         super(application);
52         mCar = Car.createCar(application);
53         mPropertyManager = mCar.getCarManager(CarPropertyManager.class);
54         // Listen for changes
55         mPropertyManager.registerCallback(this, VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS,
56                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
57         mPropertyManager.registerCallback(this, VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE,
58                 CarPropertyManager.SENSOR_RATE_ONCHANGE);
59 
60     }
61 
62     @Override
onCleared()63     protected void onCleared() {
64         super.onCleared();
65         if (DEBUG) {
66             Log.v(TAG, "onCleared()");
67         }
68         if (mCar != null && mCar.isConnected()) {
69             mCar.disconnect();
70             mCar = null;
71         }
72         if (mPropertyManager != null) {
73             mPropertyManager.unregisterCallback(this,
74                     VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS);
75             mPropertyManager.unregisterCallback(this,
76                     VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE);
77             mPropertyManager = null;
78         }
79     }
80 
81     /**
82      * @return CarPropertyValue for VehicleAreaSeat.SEAT_UNKNOWN or null if CarPropertyManager is
83      *     not initialized
84      */
85     @VisibleForTesting
86     @Nullable
getCarPropertyValue(int propertyId)87     CarPropertyValue getCarPropertyValue(int propertyId) {
88         if (mPropertyManager == null) {
89             return null;
90         }
91         return mPropertyManager.getProperty(propertyId, mPropertyManager.getAreaId(
92                 propertyId, VehicleAreaSeat.SEAT_UNKNOWN));
93     }
94 
95     @Override
onChangeEvent(CarPropertyValue carPropertyValue)96     public void onChangeEvent(CarPropertyValue carPropertyValue) {
97         if (DEBUG) {
98             Log.v(TAG, "onChangeEvent(carPropertyValue=" + carPropertyValue + ")");
99         }
100         if (carPropertyValue == null) {
101             return;
102         }
103         if (carPropertyValue.getPropertyId() == VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS) {
104             handleUnitChange(carPropertyValue);
105         } else if (carPropertyValue.getPropertyId() == VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE) {
106             handleValueChange(carPropertyValue);
107         }
108     }
109 
110     @VisibleForTesting
handleUnitChange(CarPropertyValue<Integer> newVehicleUnit)111     void handleUnitChange(CarPropertyValue<Integer> newVehicleUnit) {
112         if (newVehicleUnit.getValue() != VehicleUnit.FAHRENHEIT
113                 && newVehicleUnit.getValue() != VehicleUnit.CELSIUS) {
114             if (DEBUG) {
115                 Log.d(TAG, "handleUnitChange: Invalid temperature unit received");
116             }
117             return;
118         }
119         MeasureUnit newMeasureUnit =
120                 newVehicleUnit.getValue() == VehicleUnit.FAHRENHEIT
121                         ? MeasureUnit.FAHRENHEIT : MeasureUnit.CELSIUS;
122         if (mUnit == newMeasureUnit) {
123             return;
124         }
125         // If temperature value has been set previously and unit has changed,
126         // convert value and post update
127         if (mIsTemperatureSet) {
128             TemperatureData temperatureData;
129             if (mUnit == MeasureUnit.FAHRENHEIT) {
130                 temperatureData = new TemperatureData.Builder().setValueFahrenheit(mValue).build();
131                 temperatureData.convertToCelsius();
132             } else {
133                 temperatureData = new TemperatureData.Builder().setValueCelsius(mValue).build();
134                 temperatureData.convertToFahrenheit();
135             }
136             mTemperatureData.setValue(temperatureData);
137         }
138         mUnit = newMeasureUnit;
139     }
140 
141     @VisibleForTesting
handleValueChange(CarPropertyValue<Float> newValue)142     void handleValueChange(CarPropertyValue<Float> newValue) {
143         if (mIsTemperatureSet && mValue == newValue.getValue()) {
144             return;
145         }
146         // Outside temperature is always received in celsius, assign default
147         if (mUnit == null) {
148             mUnit = MeasureUnit.CELSIUS;
149         }
150         TemperatureData temperatureData =
151                 new TemperatureData.Builder().setValueCelsius(newValue.getValue()).build();
152         if (mUnit != MeasureUnit.CELSIUS) {
153             temperatureData.convertToFahrenheit();
154         }
155         mTemperatureData.setValue(temperatureData);
156         mValue = newValue.getValue();
157         if (!mIsTemperatureSet) {
158             mIsTemperatureSet = true;
159         }
160     }
161 
162     @Override
onErrorEvent(int propertyId, int areaId)163     public void onErrorEvent(int propertyId, int areaId) {
164         if (DEBUG) {
165             Log.w(TAG, "onErrorEvent(propertyId=" + propertyId + ", areaId=" + areaId);
166         }
167         mTemperatureData.setValue(null);
168     }
169 
getTemperatureData()170     public LiveData<TemperatureData> getTemperatureData() {
171         return mTemperatureData;
172     }
173 }
174