1 /* 2 * Copyright (C) 2021 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.VehicleAreaType.VEHICLE_AREA_TYPE_SEAT; 21 import static android.car.VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM; 22 import static android.car.VehiclePropertyIds.HVAC_AC_ON; 23 import static android.car.VehiclePropertyIds.HVAC_AUTO_ON; 24 import static android.car.VehiclePropertyIds.HVAC_AUTO_RECIRC_ON; 25 import static android.car.VehiclePropertyIds.HVAC_DEFROSTER; 26 import static android.car.VehiclePropertyIds.HVAC_DUAL_ON; 27 import static android.car.VehiclePropertyIds.HVAC_ELECTRIC_DEFROSTER_ON; 28 import static android.car.VehiclePropertyIds.HVAC_FAN_DIRECTION; 29 import static android.car.VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE; 30 import static android.car.VehiclePropertyIds.HVAC_FAN_SPEED; 31 import static android.car.VehiclePropertyIds.HVAC_MAX_AC_ON; 32 import static android.car.VehiclePropertyIds.HVAC_MAX_DEFROST_ON; 33 import static android.car.VehiclePropertyIds.HVAC_POWER_ON; 34 import static android.car.VehiclePropertyIds.HVAC_RECIRC_ON; 35 import static android.car.VehiclePropertyIds.HVAC_SEAT_TEMPERATURE; 36 import static android.car.VehiclePropertyIds.HVAC_SEAT_VENTILATION; 37 import static android.car.VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT; 38 import static android.car.VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT; 39 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT; 40 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS; 41 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_SET; 42 43 import android.annotation.IntDef; 44 import android.annotation.Nullable; 45 import android.car.Car; 46 import android.car.VehiclePropertyIds; 47 import android.car.VehicleUnit; 48 import android.car.hardware.CarPropertyConfig; 49 import android.car.hardware.CarPropertyValue; 50 import android.car.hardware.property.CarPropertyManager; 51 import android.content.res.Resources; 52 import android.os.Build; 53 import android.util.Log; 54 import android.util.SparseBooleanArray; 55 import android.view.View; 56 import android.view.ViewGroup; 57 58 import androidx.annotation.GuardedBy; 59 import androidx.annotation.VisibleForTesting; 60 61 import com.android.systemui.car.CarServiceProvider; 62 import com.android.systemui.dagger.qualifiers.Main; 63 import com.android.systemui.dagger.qualifiers.UiBackground; 64 import com.android.systemui.statusbar.policy.ConfigurationController; 65 66 import java.lang.annotation.ElementType; 67 import java.lang.annotation.Target; 68 import java.util.ArrayList; 69 import java.util.HashMap; 70 import java.util.List; 71 import java.util.Map; 72 import java.util.concurrent.Executor; 73 74 import javax.inject.Inject; 75 76 /** 77 * A controller that connects to {@link CarPropertyManager} to subscribe to HVAC property change 78 * events and propagate them to subscribing {@link HvacView}s by property ID and area ID. 79 * 80 * Grants {@link HvacView}s access to {@link HvacPropertySetter} with API's to write new values 81 * for HVAC properties. 82 */ 83 public class HvacController implements HvacPropertySetter, 84 ConfigurationController.ConfigurationListener { 85 private static final String TAG = HvacController.class.getSimpleName(); 86 private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG; 87 private static final int[] HVAC_PROPERTIES = 88 {HVAC_FAN_SPEED, HVAC_FAN_DIRECTION, HVAC_TEMPERATURE_CURRENT, HVAC_TEMPERATURE_SET, 89 HVAC_DEFROSTER, HVAC_AC_ON, HVAC_MAX_AC_ON, HVAC_MAX_DEFROST_ON, HVAC_RECIRC_ON, 90 HVAC_DUAL_ON, HVAC_AUTO_ON, HVAC_SEAT_TEMPERATURE, HVAC_SIDE_MIRROR_HEAT, 91 HVAC_STEERING_WHEEL_HEAT, HVAC_TEMPERATURE_DISPLAY_UNITS, 92 HVAC_ACTUAL_FAN_SPEED_RPM, HVAC_POWER_ON, HVAC_FAN_DIRECTION_AVAILABLE, 93 HVAC_AUTO_RECIRC_ON, HVAC_SEAT_VENTILATION, HVAC_ELECTRIC_DEFROSTER_ON}; 94 private static final int[] HVAC_PROPERTIES_TO_GET_ON_INIT = 95 {HVAC_POWER_ON, HVAC_AUTO_ON, HVAC_FAN_DIRECTION_AVAILABLE}; 96 private static final int GLOBAL_AREA_ID = 0; 97 98 @IntDef(value = {HVAC_FAN_SPEED, HVAC_FAN_DIRECTION, HVAC_TEMPERATURE_CURRENT, 99 HVAC_TEMPERATURE_SET, HVAC_DEFROSTER, HVAC_AC_ON, HVAC_MAX_AC_ON, HVAC_MAX_DEFROST_ON, 100 HVAC_RECIRC_ON, HVAC_DUAL_ON, HVAC_AUTO_ON, HVAC_SEAT_TEMPERATURE, 101 HVAC_SIDE_MIRROR_HEAT, HVAC_STEERING_WHEEL_HEAT, HVAC_TEMPERATURE_DISPLAY_UNITS, 102 HVAC_ACTUAL_FAN_SPEED_RPM, HVAC_POWER_ON, HVAC_FAN_DIRECTION_AVAILABLE, 103 HVAC_AUTO_RECIRC_ON, HVAC_SEAT_VENTILATION, HVAC_ELECTRIC_DEFROSTER_ON}) 104 @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 105 public @interface HvacProperty { 106 } 107 108 @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 109 public @interface AreaId { 110 } 111 112 private Executor mExecutor; 113 private CarPropertyManager mCarPropertyManager; 114 private boolean mIsConnectedToCar; 115 private List<Integer> mHvacPowerDependentProperties; 116 117 private final Object mLock = new Object(); 118 @GuardedBy("mLock") 119 private final SparseBooleanArray mAreaIdToIsHvacPowerOn = new SparseBooleanArray(); 120 121 /** 122 * Contains views to init until car service is connected. 123 * This must be accessed via {@link #mExecutor} to ensure thread safety. 124 */ 125 private final ArrayList<View> mViewsToInit = new ArrayList<>(); 126 @GuardedBy("itself") 127 private final Map<@HvacProperty Integer, Map<@AreaId Integer, List<HvacView>>> 128 mHvacPropertyViewMap = new HashMap<>(); 129 130 private final CarPropertyManager.CarPropertyEventCallback mPropertyEventCallback = 131 new CarPropertyManager.CarPropertyEventCallback() { 132 @Override 133 public void onChangeEvent(CarPropertyValue value) { 134 mExecutor.execute(() -> { 135 handleHvacPropertyChange(value.getPropertyId(), value); 136 }); 137 } 138 139 @Override 140 public void onErrorEvent(int propId, int zone) { 141 Log.w(TAG, "Could not handle " + propId + " change event in zone " + zone); 142 } 143 }; 144 145 @UiBackground 146 @VisibleForTesting 147 final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = 148 car -> { 149 try { 150 mExecutor.execute(() -> { 151 mIsConnectedToCar = true; 152 mCarPropertyManager = 153 (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE); 154 CarPropertyConfig hvacPowerOnConfig = 155 mCarPropertyManager.getCarPropertyConfig(HVAC_POWER_ON); 156 mHvacPowerDependentProperties = hvacPowerOnConfig != null 157 ? hvacPowerOnConfig.getConfigArray() : new ArrayList<>(); 158 registerHvacPropertyEventListeners(); 159 mViewsToInit.forEach(this::registerHvacViews); 160 mViewsToInit.clear(); 161 }); 162 } catch (Exception e) { 163 Log.e(TAG, "Failed to connect to HVAC", e); 164 mIsConnectedToCar = false; 165 } 166 }; 167 168 @Inject HvacController(CarServiceProvider carServiceProvider, @UiBackground Executor executor, @Main Resources resources, ConfigurationController configurationController)169 public HvacController(CarServiceProvider carServiceProvider, 170 @UiBackground Executor executor, 171 @Main Resources resources, 172 ConfigurationController configurationController) { 173 mExecutor = executor; 174 if (!mIsConnectedToCar) { 175 carServiceProvider.addListener(mCarServiceLifecycleListener); 176 } 177 configurationController.addCallback(this); 178 } 179 getSupportedAreaIds(int propertyId)180 private int[] getSupportedAreaIds(int propertyId) { 181 CarPropertyConfig config = mCarPropertyManager.getCarPropertyConfig(propertyId); 182 if (config == null) { 183 // This property isn't supported/exposed by the CarPropertyManager. So an empty array is 184 // returned here to signify that no areaIds with this propertyId are going to be 185 // registered or updated. 186 return new int[] {}; 187 } 188 return config.getAreaIds(); 189 } 190 getAreaIdsFromTargetAreaId(int propertyId, int targetAreaId)191 private ArrayList<Integer> getAreaIdsFromTargetAreaId(int propertyId, int targetAreaId) { 192 ArrayList<Integer> areaIdsFromTargetAreaId = new ArrayList<Integer>(); 193 int[] supportedAreaIds = getSupportedAreaIds(propertyId); 194 195 for (int supportedAreaId : supportedAreaIds) { 196 if (targetAreaId == GLOBAL_AREA_ID || (targetAreaId & supportedAreaId) != 0) { 197 areaIdsFromTargetAreaId.add(supportedAreaId); 198 } 199 } 200 201 return areaIdsFromTargetAreaId; 202 } 203 204 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, int val)205 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 206 int val) { 207 mExecutor.execute(() -> { 208 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 209 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 210 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 211 + Integer.toHexString(targetAreaId) + ", val: " + val); 212 return; 213 } 214 try { 215 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 216 propertyId.intValue(), targetAreaId); 217 for (int areaId : supportedAreaIds) { 218 mCarPropertyManager.setIntProperty(propertyId, areaId, val); 219 } 220 } catch (RuntimeException e) { 221 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 222 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 223 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 224 } 225 }); 226 } 227 228 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, float val)229 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 230 float val) { 231 mExecutor.execute(() -> { 232 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 233 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 234 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 235 + Integer.toHexString(targetAreaId) + ", val: " + val); 236 return; 237 } 238 try { 239 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 240 propertyId.intValue(), targetAreaId); 241 for (int areaId : supportedAreaIds) { 242 mCarPropertyManager.setFloatProperty(propertyId, areaId, val); 243 } 244 } catch (RuntimeException e) { 245 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 246 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 247 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 248 } 249 }); 250 } 251 252 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, boolean val)253 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 254 boolean val) { 255 mExecutor.execute(() -> { 256 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 257 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 258 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 259 + Integer.toHexString(targetAreaId) + ", val: " + val); 260 return; 261 } 262 try { 263 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 264 propertyId.intValue(), targetAreaId); 265 for (int areaId : supportedAreaIds) { 266 mCarPropertyManager.setBooleanProperty(propertyId, areaId, val); 267 } 268 } catch (RuntimeException e) { 269 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 270 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 271 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 272 } 273 }); 274 } 275 276 /** 277 * Registers all {@link HvacView}s in the {@code rootView} and its descendents. 278 */ 279 @UiBackground registerHvacViews(View rootView)280 public void registerHvacViews(View rootView) { 281 if (!mIsConnectedToCar) { 282 mExecutor.execute(() -> mViewsToInit.add(rootView)); 283 return; 284 } 285 286 if (rootView instanceof HvacView) { 287 try { 288 HvacView hvacView = (HvacView) rootView; 289 @HvacProperty Integer propId = hvacView.getHvacPropertyToView(); 290 @AreaId Integer targetAreaId = hvacView.getAreaId(); 291 292 CarPropertyConfig carPropertyConfig = 293 mCarPropertyManager.getCarPropertyConfig(propId); 294 if (carPropertyConfig == null) { 295 throw new IllegalArgumentException( 296 "Cannot register hvac view for property: " 297 + VehiclePropertyIds.toString(propId) 298 + " because property is not implemented."); 299 } 300 301 hvacView.setHvacPropertySetter(this); 302 hvacView.setConfigInfo(carPropertyConfig); 303 hvacView.setDisableViewIfPowerOff(mHvacPowerDependentProperties.contains(propId)); 304 305 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId(propId.intValue(), 306 targetAreaId.intValue()); 307 for (Integer areaId : supportedAreaIds) { 308 addHvacViewToMap(propId.intValue(), areaId.intValue(), hvacView); 309 } 310 311 if (mCarPropertyManager != null) { 312 CarPropertyValue<Integer> hvacTemperatureDisplayUnitsValue = 313 (CarPropertyValue<Integer>) getPropertyValueOrNull( 314 HVAC_TEMPERATURE_DISPLAY_UNITS, GLOBAL_AREA_ID); 315 for (Integer areaId : supportedAreaIds) { 316 CarPropertyValue initValueOrNull = getPropertyValueOrNull(propId, areaId); 317 318 // Initialize the view with the initial value. 319 if (initValueOrNull != null) { 320 hvacView.onPropertyChanged(initValueOrNull); 321 } 322 if (hvacTemperatureDisplayUnitsValue != null) { 323 boolean usesFahrenheit = hvacTemperatureDisplayUnitsValue.getValue() 324 == VehicleUnit.FAHRENHEIT; 325 hvacView.onHvacTemperatureUnitChanged(usesFahrenheit); 326 } 327 328 if (carPropertyConfig.getAreaType() != VEHICLE_AREA_TYPE_SEAT) { 329 continue; 330 } 331 332 for (int propToGetOnInitId : HVAC_PROPERTIES_TO_GET_ON_INIT) { 333 int[] propToGetOnInitSupportedAreaIds = getSupportedAreaIds( 334 propToGetOnInitId); 335 336 int areaIdToFind = areaId.intValue(); 337 338 for (int supportedAreaId : propToGetOnInitSupportedAreaIds) { 339 if ((supportedAreaId & areaIdToFind) == areaIdToFind) { 340 CarPropertyValue propToGetOnInitValueOrNull = 341 getPropertyValueOrNull(propToGetOnInitId, 342 supportedAreaId); 343 if (propToGetOnInitValueOrNull != null) { 344 hvacView.onPropertyChanged(propToGetOnInitValueOrNull); 345 } 346 break; 347 } 348 } 349 } 350 } 351 } 352 } catch (IllegalArgumentException ex) { 353 Log.e(TAG, "Can't register HVAC view", ex); 354 } 355 } 356 357 if (rootView instanceof ViewGroup) { 358 ViewGroup viewGroup = (ViewGroup) rootView; 359 for (int i = 0; i < viewGroup.getChildCount(); i++) { 360 registerHvacViews(viewGroup.getChildAt(i)); 361 } 362 } 363 } 364 365 /** 366 * Unregisters all {@link HvacView}s in the {@code rootView} and its descendents. 367 */ unregisterViews(View rootView)368 public void unregisterViews(View rootView) { 369 if (rootView instanceof HvacView) { 370 HvacView hvacView = (HvacView) rootView; 371 @HvacProperty Integer propId = hvacView.getHvacPropertyToView(); 372 @AreaId Integer targetAreaId = hvacView.getAreaId(); 373 374 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId(propId.intValue(), 375 targetAreaId.intValue()); 376 for (Integer areaId : supportedAreaIds) { 377 removeHvacViewFromMap(propId.intValue(), areaId.intValue(), hvacView); 378 } 379 } 380 381 if (rootView instanceof ViewGroup) { 382 ViewGroup viewGroup = (ViewGroup) rootView; 383 for (int i = 0; i < viewGroup.getChildCount(); i++) { 384 unregisterViews(viewGroup.getChildAt(i)); 385 } 386 } 387 } 388 389 @VisibleForTesting handleHvacPropertyChange(@vacProperty int propertyId, CarPropertyValue value)390 void handleHvacPropertyChange(@HvacProperty int propertyId, CarPropertyValue value) { 391 if (DEBUG) { 392 Log.d(TAG, "handleHvacPropertyChange - propertyId: " 393 + VehiclePropertyIds.toString(propertyId) + " value: " + value); 394 } 395 if (value.getPropertyId() == HVAC_POWER_ON) { 396 handleHvacPowerOn(value); 397 } 398 if (value.getPropertyId() == HVAC_TEMPERATURE_DISPLAY_UNITS) { 399 synchronized (mHvacPropertyViewMap) { 400 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 401 areaIds.forEach((areaId, views) -> { 402 views.forEach(v -> v.onHvacTemperatureUnitChanged( 403 (Integer) value.getValue() == VehicleUnit.FAHRENHEIT)); 404 }); 405 }); 406 } 407 return; 408 } 409 410 int valueAreaType = mCarPropertyManager.getCarPropertyConfig(value.getPropertyId()) 411 .getAreaType(); 412 if (valueAreaType == VEHICLE_AREA_TYPE_GLOBAL) { 413 synchronized (mHvacPropertyViewMap) { 414 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 415 areaIds.forEach((areaId, views) -> { 416 views.forEach(v -> v.onPropertyChanged(value)); 417 }); 418 }); 419 } 420 } else { 421 synchronized (mHvacPropertyViewMap) { 422 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 423 if (valueAreaType 424 == mCarPropertyManager.getCarPropertyConfig(propId).getAreaType()) { 425 areaIds.forEach((areaId, views) -> { 426 if ((value.getAreaId() & areaId) == areaId) { 427 views.forEach(v -> v.onPropertyChanged(value)); 428 } 429 }); 430 } 431 }); 432 } 433 } 434 } 435 436 @VisibleForTesting getHvacPropertyViewMap()437 Map<@HvacProperty Integer, Map<@AreaId Integer, List<HvacView>>> getHvacPropertyViewMap() { 438 return mHvacPropertyViewMap; 439 } 440 441 @Override onLocaleListChanged()442 public void onLocaleListChanged() { 443 // Call {@link HvacView#onLocaleListChanged} on all {@link HvacView} instances. 444 synchronized (mHvacPropertyViewMap) { 445 for (Map<@AreaId Integer, List<HvacView>> subMap : mHvacPropertyViewMap.values()) { 446 for (List<HvacView> views : subMap.values()) { 447 for (HvacView view : views) { 448 view.onLocaleListChanged(); 449 } 450 } 451 } 452 } 453 } 454 handleHvacPowerOn(CarPropertyValue hvacPowerOnValue)455 private void handleHvacPowerOn(CarPropertyValue hvacPowerOnValue) { 456 Boolean isPowerOn = (Boolean) hvacPowerOnValue.getValue(); 457 synchronized (mLock) { 458 mAreaIdToIsHvacPowerOn.put(hvacPowerOnValue.getAreaId(), isPowerOn); 459 } 460 if (!isPowerOn) { 461 return; 462 } 463 464 for (int propertyId: mHvacPowerDependentProperties) { 465 mExecutor.execute(() -> { 466 ArrayList<Integer> areaIds = getAreaIdsFromTargetAreaId(propertyId, 467 hvacPowerOnValue.getAreaId()); 468 for (int areaId: areaIds) { 469 CarPropertyValue valueOrNull = getPropertyValueOrNull(propertyId, areaId); 470 if (valueOrNull != null) { 471 handleHvacPropertyChange(propertyId, valueOrNull); 472 } 473 } 474 }); 475 } 476 } 477 478 @Nullable getPropertyValueOrNull(int propertyId, int areaId)479 private CarPropertyValue<?> getPropertyValueOrNull(int propertyId, int areaId) { 480 if (isHvacPowerDependentPropAndNotAvailable(propertyId, areaId)) { 481 return null; 482 } 483 try { 484 return mCarPropertyManager.getProperty(propertyId, areaId); 485 } catch (Exception e) { 486 Log.e(TAG, "getPropertyValueOrNull - Error while getting HVAC propertyId: " 487 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 488 + Integer.toHexString(areaId) + ": ", e); 489 } 490 return null; 491 } 492 isHvacPowerDependentPropAndNotAvailable(int propertyId, int areaId)493 private boolean isHvacPowerDependentPropAndNotAvailable(int propertyId, int areaId) { 494 if (!mHvacPowerDependentProperties.contains(propertyId)) { 495 return false; 496 } 497 ArrayList<Integer> powerDependentAreaIds = getAreaIdsFromTargetAreaId(propertyId, areaId); 498 synchronized (mLock) { 499 for (int powerDependentAreaId: powerDependentAreaIds) { 500 for (int i = 0; i < mAreaIdToIsHvacPowerOn.size(); ++i) { 501 if ((mAreaIdToIsHvacPowerOn.keyAt(i) & powerDependentAreaId) 502 == powerDependentAreaId) { 503 return !mAreaIdToIsHvacPowerOn.valueAt(i); 504 } 505 } 506 } 507 } 508 Log.w(TAG, "isHvacPowerDependentPropAndNotAvailable - For propertyId: + " 509 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 510 + Integer.toHexString(areaId) + ", no matching area ID found for HVAC_POWER_ON."); 511 return false; 512 } 513 registerHvacPropertyEventListeners()514 private void registerHvacPropertyEventListeners() { 515 for (int i = 0; i < HVAC_PROPERTIES.length; i++) { 516 @HvacProperty Integer propertyId = HVAC_PROPERTIES[i]; 517 if (mCarPropertyManager.getCarPropertyConfig(propertyId) == null) { 518 Log.w(TAG, "registerHvacPropertyEventListeners - propertyId: + " 519 + VehiclePropertyIds.toString(propertyId) + " is not implemented." 520 + " Skipping registering callback."); 521 continue; 522 } 523 mCarPropertyManager.registerCallback(mPropertyEventCallback, propertyId, 524 CarPropertyManager.SENSOR_RATE_ONCHANGE); 525 } 526 } 527 addHvacViewToMap(@vacProperty int propId, @AreaId int areaId, HvacView v)528 private void addHvacViewToMap(@HvacProperty int propId, @AreaId int areaId, 529 HvacView v) { 530 synchronized (mHvacPropertyViewMap) { 531 mHvacPropertyViewMap.computeIfAbsent(propId, k -> new HashMap<>()) 532 .computeIfAbsent(areaId, k -> new ArrayList<>()) 533 .add(v); 534 } 535 } 536 removeHvacViewFromMap(@vacProperty int propId, @AreaId int areaId, HvacView v)537 private void removeHvacViewFromMap(@HvacProperty int propId, @AreaId int areaId, HvacView v) { 538 synchronized (mHvacPropertyViewMap) { 539 Map<Integer, List<HvacView>> viewsRegisteredForProp = mHvacPropertyViewMap.get(propId); 540 if (viewsRegisteredForProp != null) { 541 List<HvacView> registeredViews = viewsRegisteredForProp.get(areaId); 542 if (registeredViews != null) { 543 registeredViews.remove(v); 544 if (registeredViews.isEmpty()) { 545 viewsRegisteredForProp.remove(areaId); 546 if (viewsRegisteredForProp.isEmpty()) { 547 mHvacPropertyViewMap.remove(propId); 548 } 549 } 550 } 551 } 552 } 553 } 554 }