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 android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 20 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS; 21 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_SET; 22 23 import android.car.Car; 24 import android.car.VehicleUnit; 25 import android.car.hardware.CarPropertyValue; 26 import android.car.hardware.property.CarPropertyManager; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.ViewGroup; 30 31 import com.android.systemui.car.CarServiceProvider; 32 import com.android.systemui.dagger.SysUISingleton; 33 import com.android.systemui.dagger.qualifiers.UiBackground; 34 import com.android.systemui.statusbar.policy.ConfigurationController; 35 36 import java.util.ArrayList; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Set; 42 import java.util.concurrent.Executor; 43 44 import javax.inject.Inject; 45 46 /** 47 * Manages the connection to the Car service and delegates value changes to the registered 48 * {@link TemperatureView}s 49 */ 50 @SysUISingleton 51 public class HvacController implements ConfigurationController.ConfigurationListener { 52 public static final String TAG = "HvacController"; 53 private static final boolean DEBUG = true; 54 55 private final Executor mBackgroundExecutor; 56 private final CarServiceProvider mCarServiceProvider; 57 private final Set<TemperatureView> mRegisteredViews = new HashSet<>(); 58 59 private CarPropertyManager mCarPropertyManager; 60 private HashMap<Integer, List<TemperatureView>> mTempComponents = new HashMap<>(); 61 62 private final CarPropertyManager.CarPropertyEventCallback mHvacTemperatureSetCallback = 63 new CarPropertyManager.CarPropertyEventCallback() { 64 @Override 65 public void onChangeEvent(CarPropertyValue value) { 66 try { 67 int areaId = value.getAreaId(); 68 List<TemperatureView> temperatureViews = mTempComponents.get(areaId); 69 if (temperatureViews != null && !temperatureViews.isEmpty()) { 70 float newTemp = (float) value.getValue(); 71 if (DEBUG) { 72 Log.d(TAG, "onChangeEvent: " + areaId + ":" + value); 73 } 74 for (TemperatureView view : temperatureViews) { 75 view.setTemp(newTemp); 76 } 77 } 78 } catch (Exception e) { 79 Log.e(TAG, "Failed handling hvac change event", e); 80 } 81 } 82 83 @Override 84 public void onErrorEvent(int propId, int zone) { 85 Log.d(TAG, "HVAC error event, propertyId: " + propId + " zone: " + zone); 86 } 87 }; 88 89 private final CarPropertyManager.CarPropertyEventCallback mTemperatureUnitChangeCallback = 90 new CarPropertyManager.CarPropertyEventCallback() { 91 @Override 92 public void onChangeEvent(CarPropertyValue value) { 93 if (!mRegisteredViews.isEmpty()) { 94 for (TemperatureView view : mRegisteredViews) { 95 view.setDisplayInFahrenheit( 96 value.getValue().equals(VehicleUnit.FAHRENHEIT)); 97 } 98 } 99 } 100 101 @Override 102 public void onErrorEvent(int propId, int zone) { 103 Log.d(TAG, "HVAC error event, propertyId: " + propId + " zone: " + zone); 104 } 105 }; 106 107 private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = 108 car -> { 109 try { 110 mCarPropertyManager = (CarPropertyManager) car.getCarManager( 111 Car.PROPERTY_SERVICE); 112 mCarPropertyManager.registerCallback(mHvacTemperatureSetCallback, 113 HVAC_TEMPERATURE_SET, CarPropertyManager.SENSOR_RATE_ONCHANGE); 114 mCarPropertyManager.registerCallback(mTemperatureUnitChangeCallback, 115 HVAC_TEMPERATURE_DISPLAY_UNITS, 116 CarPropertyManager.SENSOR_RATE_ONCHANGE); 117 initComponents(); 118 } catch (Exception e) { 119 Log.e(TAG, "Failed to correctly connect to HVAC", e); 120 } 121 }; 122 123 @Inject HvacController(CarServiceProvider carServiceProvider, @UiBackground Executor backgroundExecutor, ConfigurationController configurationController)124 public HvacController(CarServiceProvider carServiceProvider, 125 @UiBackground Executor backgroundExecutor, 126 ConfigurationController configurationController) { 127 mCarServiceProvider = carServiceProvider; 128 mBackgroundExecutor = backgroundExecutor; 129 configurationController.addCallback(this); 130 } 131 132 /** 133 * Create connection to the Car service. 134 */ connectToCarService()135 public void connectToCarService() { 136 mCarServiceProvider.addListener(mCarServiceLifecycleListener); 137 } 138 139 /** 140 * Add component to list and initialize it if the connection is up. 141 */ addHvacTextView(TemperatureView temperatureView)142 private void addHvacTextView(TemperatureView temperatureView) { 143 if (mRegisteredViews.contains(temperatureView)) { 144 return; 145 } 146 147 int areaId = temperatureView.getAreaId(); 148 if (!mTempComponents.containsKey(areaId)) { 149 mTempComponents.put(areaId, new ArrayList<>()); 150 } 151 mTempComponents.get(areaId).add(temperatureView); 152 initComponent(temperatureView); 153 154 mRegisteredViews.add(temperatureView); 155 } 156 initComponents()157 private void initComponents() { 158 for (Map.Entry<Integer, List<TemperatureView>> next : mTempComponents.entrySet()) { 159 List<TemperatureView> temperatureViews = next.getValue(); 160 for (TemperatureView view : temperatureViews) { 161 initComponent(view); 162 } 163 } 164 } 165 initComponent(TemperatureView view)166 private void initComponent(TemperatureView view) { 167 int zone = view.getAreaId(); 168 if (DEBUG) { 169 Log.d(TAG, "initComponent: " + zone); 170 } 171 172 try { 173 if (mCarPropertyManager != null && mCarPropertyManager.isPropertyAvailable( 174 HVAC_TEMPERATURE_DISPLAY_UNITS, VEHICLE_AREA_TYPE_GLOBAL)) { 175 if (mCarPropertyManager.getIntProperty(HVAC_TEMPERATURE_DISPLAY_UNITS, 176 VEHICLE_AREA_TYPE_GLOBAL) == VehicleUnit.FAHRENHEIT) { 177 view.setDisplayInFahrenheit(true); 178 } 179 } 180 if (mCarPropertyManager == null || !mCarPropertyManager.isPropertyAvailable( 181 HVAC_TEMPERATURE_SET, zone)) { 182 view.setTemp(Float.NaN); 183 return; 184 } 185 view.setTemp( 186 mCarPropertyManager.getFloatProperty(HVAC_TEMPERATURE_SET, zone)); 187 view.setHvacController(this); 188 } catch (Exception e) { 189 view.setTemp(Float.NaN); 190 Log.e(TAG, "Failed to get value from hvac service", e); 191 } 192 } 193 194 /** 195 * Removes all registered components. This is useful if you need to rebuild the UI since 196 * components self register. 197 */ removeAllComponents()198 public void removeAllComponents() { 199 mTempComponents.clear(); 200 mRegisteredViews.clear(); 201 } 202 203 /** 204 * Iterate through a view, looking for {@link TemperatureView} instances and add them to the 205 * controller if found. 206 */ addTemperatureViewToController(View v)207 public void addTemperatureViewToController(View v) { 208 if (v instanceof TemperatureView) { 209 addHvacTextView((TemperatureView) v); 210 } else if (v instanceof ViewGroup) { 211 ViewGroup viewGroup = (ViewGroup) v; 212 for (int i = 0; i < viewGroup.getChildCount(); i++) { 213 addTemperatureViewToController(viewGroup.getChildAt(i)); 214 } 215 } 216 } 217 218 /** 219 * Set the temperature in Celsius of the specified zone 220 */ setTemperature(float tempC, int zone)221 public void setTemperature(float tempC, int zone) { 222 if (mCarPropertyManager != null) { 223 // Internally, all temperatures are represented in floating point Celsius 224 mBackgroundExecutor.execute( 225 () -> mCarPropertyManager.setFloatProperty(HVAC_TEMPERATURE_SET, zone, tempC)); 226 } 227 } 228 229 @Override onLocaleListChanged()230 public void onLocaleListChanged() { 231 for (TemperatureView view : mRegisteredViews) { 232 view.onLocaleListChanged(); 233 } 234 } 235 236 /** 237 * Convert the given temperature in Celsius into Fahrenheit 238 * 239 * @param tempC - The temperature in Celsius 240 * @return Temperature in Fahrenheit. 241 */ convertToFahrenheit(float tempC)242 public static float convertToFahrenheit(float tempC) { 243 return (tempC * 9f / 5f) + 32; 244 } 245 246 /** 247 * Convert the given temperature in Fahrenheit to Celsius 248 * 249 * @param tempF - The temperature in Fahrenheit. 250 * @return Temperature in Celsius. 251 */ convertToCelsius(float tempF)252 public static float convertToCelsius(float tempF) { 253 return (tempF - 32) * 5f / 9f; 254 } 255 } 256