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 @VisibleForTesting 146 final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = 147 car -> { 148 mExecutor.execute(() -> { 149 try { 150 mIsConnectedToCar = true; 151 mCarPropertyManager = 152 (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE); 153 CarPropertyConfig hvacPowerOnConfig = 154 mCarPropertyManager.getCarPropertyConfig(HVAC_POWER_ON); 155 if (hvacPowerOnConfig != null 156 && hvacPowerOnConfig.getConfigArray() != null) { 157 mHvacPowerDependentProperties = hvacPowerOnConfig.getConfigArray(); 158 } else { 159 Log.w(TAG, "CarPropertyConfig#getConfigArray is null"); 160 mHvacPowerDependentProperties = new ArrayList<>(); 161 } 162 registerHvacPropertyEventListeners(); 163 mViewsToInit.forEach(this::registerHvacViews); 164 mViewsToInit.clear(); 165 } catch (Exception e) { 166 Log.e(TAG, "Failed to connect to HVAC", e); 167 mIsConnectedToCar = false; 168 } 169 }); 170 }; 171 172 @Inject HvacController(CarServiceProvider carServiceProvider, @UiBackground Executor executor, @Main Resources resources, ConfigurationController configurationController)173 public HvacController(CarServiceProvider carServiceProvider, 174 @UiBackground Executor executor, 175 @Main Resources resources, 176 ConfigurationController configurationController) { 177 mExecutor = executor; 178 carServiceProvider.addListener(mCarServiceLifecycleListener); 179 configurationController.addCallback(this); 180 } 181 getSupportedAreaIds(int propertyId)182 private int[] getSupportedAreaIds(int propertyId) { 183 if (mCarPropertyManager == null) { 184 return new int[] {}; 185 } 186 CarPropertyConfig config = mCarPropertyManager.getCarPropertyConfig(propertyId); 187 if (config == null) { 188 // This property isn't supported/exposed by the CarPropertyManager. So an empty array is 189 // returned here to signify that no areaIds with this propertyId are going to be 190 // registered or updated. 191 return new int[] {}; 192 } 193 return config.getAreaIds(); 194 } 195 getAreaIdsFromTargetAreaId(int propertyId, int targetAreaId)196 private ArrayList<Integer> getAreaIdsFromTargetAreaId(int propertyId, int targetAreaId) { 197 ArrayList<Integer> areaIdsFromTargetAreaId = new ArrayList<Integer>(); 198 int[] supportedAreaIds = getSupportedAreaIds(propertyId); 199 200 for (int supportedAreaId : supportedAreaIds) { 201 if (targetAreaId == GLOBAL_AREA_ID || (targetAreaId & supportedAreaId) != 0) { 202 areaIdsFromTargetAreaId.add(supportedAreaId); 203 } 204 } 205 206 return areaIdsFromTargetAreaId; 207 } 208 209 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, int val)210 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 211 int val) { 212 mExecutor.execute(() -> { 213 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 214 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 215 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 216 + Integer.toHexString(targetAreaId) + ", val: " + val); 217 return; 218 } 219 try { 220 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 221 propertyId.intValue(), targetAreaId); 222 for (int areaId : supportedAreaIds) { 223 mCarPropertyManager.setIntProperty(propertyId, areaId, val); 224 } 225 } catch (RuntimeException e) { 226 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 227 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 228 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 229 } 230 }); 231 } 232 233 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, float val)234 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 235 float val) { 236 mExecutor.execute(() -> { 237 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 238 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 239 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 240 + Integer.toHexString(targetAreaId) + ", val: " + val); 241 return; 242 } 243 try { 244 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 245 propertyId.intValue(), targetAreaId); 246 for (int areaId : supportedAreaIds) { 247 mCarPropertyManager.setFloatProperty(propertyId, areaId, val); 248 } 249 } catch (RuntimeException e) { 250 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 251 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 252 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 253 } 254 }); 255 } 256 257 @Override setHvacProperty(@vacProperty Integer propertyId, int targetAreaId, boolean val)258 public void setHvacProperty(@HvacProperty Integer propertyId, int targetAreaId, 259 boolean val) { 260 mExecutor.execute(() -> { 261 if (isHvacPowerDependentPropAndNotAvailable(propertyId.intValue(), targetAreaId)) { 262 Log.w(TAG, "setHvacProperty - HVAC_POWER_ON is false so skipping setting HVAC" 263 + " propertyId: " + VehiclePropertyIds.toString(propertyId) + ", areaId: " 264 + Integer.toHexString(targetAreaId) + ", val: " + val); 265 return; 266 } 267 try { 268 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId( 269 propertyId.intValue(), targetAreaId); 270 for (int areaId : supportedAreaIds) { 271 mCarPropertyManager.setBooleanProperty(propertyId, areaId, val); 272 } 273 } catch (RuntimeException e) { 274 Log.w(TAG, "setHvacProperty - Error while setting HVAC propertyId: " 275 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 276 + Integer.toHexString(targetAreaId) + ", val: " + val, e); 277 } 278 }); 279 } 280 281 /** 282 * Registers all {@link HvacView}s in the {@code rootView} and its descendents. 283 */ registerHvacViews(View rootView)284 public void registerHvacViews(View rootView) { 285 mExecutor.execute(() -> { 286 if (!mIsConnectedToCar) { 287 mViewsToInit.add(rootView); 288 return; 289 } 290 291 if (rootView instanceof HvacView) { 292 try { 293 HvacView hvacView = (HvacView) rootView; 294 @HvacProperty Integer propId = hvacView.getHvacPropertyToView(); 295 @AreaId Integer targetAreaId = hvacView.getAreaId(); 296 297 CarPropertyConfig carPropertyConfig = 298 mCarPropertyManager.getCarPropertyConfig(propId); 299 if (carPropertyConfig == null) { 300 throw new IllegalArgumentException( 301 "Cannot register hvac view for property: " 302 + VehiclePropertyIds.toString(propId) 303 + " because property is not implemented."); 304 } 305 306 hvacView.setHvacPropertySetter(this); 307 hvacView.setConfigInfo(carPropertyConfig); 308 hvacView.setDisableViewIfPowerOff( 309 mHvacPowerDependentProperties.contains(propId)); 310 311 ArrayList<Integer> supportedAreaIds = 312 getAreaIdsFromTargetAreaId(propId.intValue(), targetAreaId.intValue()); 313 for (Integer areaId : supportedAreaIds) { 314 addHvacViewToMap(propId.intValue(), areaId.intValue(), hvacView); 315 } 316 317 if (mCarPropertyManager != null) { 318 CarPropertyValue<Integer> hvacTemperatureDisplayUnitsValue = 319 (CarPropertyValue<Integer>) getPropertyValueOrNull( 320 HVAC_TEMPERATURE_DISPLAY_UNITS, GLOBAL_AREA_ID); 321 for (Integer areaId : supportedAreaIds) { 322 CarPropertyValue initValueOrNull = 323 getPropertyValueOrNull(propId, areaId); 324 325 // Initialize the view with the initial value. 326 if (initValueOrNull != null) { 327 hvacView.onPropertyChanged(initValueOrNull); 328 } 329 if (hvacTemperatureDisplayUnitsValue != null) { 330 boolean usesFahrenheit = hvacTemperatureDisplayUnitsValue.getValue() 331 == VehicleUnit.FAHRENHEIT; 332 hvacView.onHvacTemperatureUnitChanged(usesFahrenheit); 333 } 334 335 if (carPropertyConfig.getAreaType() != VEHICLE_AREA_TYPE_SEAT) { 336 continue; 337 } 338 339 for (int propToGetOnInitId : HVAC_PROPERTIES_TO_GET_ON_INIT) { 340 int[] propToGetOnInitSupportedAreaIds = getSupportedAreaIds( 341 propToGetOnInitId); 342 343 int areaIdToFind = areaId.intValue(); 344 345 for (int supportedAreaId : propToGetOnInitSupportedAreaIds) { 346 if ((supportedAreaId & areaIdToFind) == areaIdToFind) { 347 CarPropertyValue propToGetOnInitValueOrNull = 348 getPropertyValueOrNull(propToGetOnInitId, 349 supportedAreaId); 350 if (propToGetOnInitValueOrNull != null) { 351 hvacView.onPropertyChanged(propToGetOnInitValueOrNull); 352 } 353 break; 354 } 355 } 356 } 357 } 358 } 359 } catch (IllegalArgumentException ex) { 360 Log.e(TAG, "Can't register HVAC view", ex); 361 } 362 } 363 }); 364 365 if (rootView instanceof ViewGroup) { 366 ViewGroup viewGroup = (ViewGroup) rootView; 367 for (int i = 0; i < viewGroup.getChildCount(); i++) { 368 registerHvacViews(viewGroup.getChildAt(i)); 369 } 370 } 371 } 372 373 /** 374 * Unregisters all {@link HvacView}s in the {@code rootView} and its descendents. 375 */ unregisterViews(View rootView)376 public void unregisterViews(View rootView) { 377 mExecutor.execute(() -> { 378 if (!mIsConnectedToCar) { 379 mViewsToInit.remove(rootView); 380 return; 381 } 382 if (rootView instanceof HvacView) { 383 HvacView hvacView = (HvacView) rootView; 384 @HvacProperty Integer propId = hvacView.getHvacPropertyToView(); 385 @AreaId Integer targetAreaId = hvacView.getAreaId(); 386 387 ArrayList<Integer> supportedAreaIds = getAreaIdsFromTargetAreaId(propId.intValue(), 388 targetAreaId.intValue()); 389 for (Integer areaId : supportedAreaIds) { 390 removeHvacViewFromMap(propId.intValue(), areaId.intValue(), hvacView); 391 } 392 } 393 }); 394 395 if (rootView instanceof ViewGroup) { 396 ViewGroup viewGroup = (ViewGroup) rootView; 397 for (int i = 0; i < viewGroup.getChildCount(); i++) { 398 unregisterViews(viewGroup.getChildAt(i)); 399 } 400 } 401 } 402 403 @VisibleForTesting handleHvacPropertyChange(@vacProperty int propertyId, CarPropertyValue value)404 void handleHvacPropertyChange(@HvacProperty int propertyId, CarPropertyValue value) { 405 if (DEBUG) { 406 Log.d(TAG, "handleHvacPropertyChange - propertyId: " 407 + VehiclePropertyIds.toString(propertyId) + " value: " + value); 408 } 409 if (value.getPropertyId() == HVAC_POWER_ON) { 410 handleHvacPowerOn(value); 411 } 412 if (value.getPropertyId() == HVAC_TEMPERATURE_DISPLAY_UNITS) { 413 synchronized (mHvacPropertyViewMap) { 414 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 415 areaIds.forEach((areaId, views) -> { 416 views.forEach(v -> v.onHvacTemperatureUnitChanged( 417 (Integer) value.getValue() == VehicleUnit.FAHRENHEIT)); 418 }); 419 }); 420 } 421 return; 422 } 423 424 int valueAreaType = mCarPropertyManager.getCarPropertyConfig(value.getPropertyId()) 425 .getAreaType(); 426 if (valueAreaType == VEHICLE_AREA_TYPE_GLOBAL) { 427 synchronized (mHvacPropertyViewMap) { 428 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 429 areaIds.forEach((areaId, views) -> { 430 views.forEach(v -> v.onPropertyChanged(value)); 431 }); 432 }); 433 } 434 } else { 435 synchronized (mHvacPropertyViewMap) { 436 mHvacPropertyViewMap.forEach((propId, areaIds) -> { 437 if (valueAreaType 438 == mCarPropertyManager.getCarPropertyConfig(propId).getAreaType()) { 439 areaIds.forEach((areaId, views) -> { 440 if ((value.getAreaId() & areaId) == areaId) { 441 views.forEach(v -> v.onPropertyChanged(value)); 442 } 443 }); 444 } 445 }); 446 } 447 } 448 } 449 450 @VisibleForTesting getHvacPropertyViewMap()451 Map<@HvacProperty Integer, Map<@AreaId Integer, List<HvacView>>> getHvacPropertyViewMap() { 452 return mHvacPropertyViewMap; 453 } 454 455 @Override onLocaleListChanged()456 public void onLocaleListChanged() { 457 // Call {@link HvacView#onLocaleListChanged} on all {@link HvacView} instances. 458 synchronized (mHvacPropertyViewMap) { 459 for (Map<@AreaId Integer, List<HvacView>> subMap : mHvacPropertyViewMap.values()) { 460 for (List<HvacView> views : subMap.values()) { 461 for (HvacView view : views) { 462 view.onLocaleListChanged(); 463 } 464 } 465 } 466 } 467 } 468 handleHvacPowerOn(CarPropertyValue hvacPowerOnValue)469 private void handleHvacPowerOn(CarPropertyValue hvacPowerOnValue) { 470 Boolean isPowerOn = (Boolean) hvacPowerOnValue.getValue(); 471 synchronized (mLock) { 472 mAreaIdToIsHvacPowerOn.put(hvacPowerOnValue.getAreaId(), isPowerOn); 473 } 474 if (!isPowerOn) { 475 return; 476 } 477 478 for (int propertyId: mHvacPowerDependentProperties) { 479 mExecutor.execute(() -> { 480 ArrayList<Integer> areaIds = getAreaIdsFromTargetAreaId(propertyId, 481 hvacPowerOnValue.getAreaId()); 482 for (int areaId: areaIds) { 483 CarPropertyValue valueOrNull = getPropertyValueOrNull(propertyId, areaId); 484 if (valueOrNull != null) { 485 handleHvacPropertyChange(propertyId, valueOrNull); 486 } 487 } 488 }); 489 } 490 } 491 492 @Nullable getPropertyValueOrNull(int propertyId, int areaId)493 private CarPropertyValue<?> getPropertyValueOrNull(int propertyId, int areaId) { 494 if (isHvacPowerDependentPropAndNotAvailable(propertyId, areaId)) { 495 return null; 496 } 497 try { 498 return mCarPropertyManager.getProperty(propertyId, areaId); 499 } catch (Exception e) { 500 Log.e(TAG, "getPropertyValueOrNull - Error while getting HVAC propertyId: " 501 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 502 + Integer.toHexString(areaId) + ": ", e); 503 } 504 return null; 505 } 506 isHvacPowerDependentPropAndNotAvailable(int propertyId, int areaId)507 private boolean isHvacPowerDependentPropAndNotAvailable(int propertyId, int areaId) { 508 if (!mHvacPowerDependentProperties.contains(propertyId)) { 509 return false; 510 } 511 ArrayList<Integer> powerDependentAreaIds = getAreaIdsFromTargetAreaId(propertyId, areaId); 512 synchronized (mLock) { 513 for (int powerDependentAreaId: powerDependentAreaIds) { 514 for (int i = 0; i < mAreaIdToIsHvacPowerOn.size(); ++i) { 515 if ((mAreaIdToIsHvacPowerOn.keyAt(i) & powerDependentAreaId) 516 == powerDependentAreaId) { 517 return !mAreaIdToIsHvacPowerOn.valueAt(i); 518 } 519 } 520 } 521 } 522 Log.w(TAG, "isHvacPowerDependentPropAndNotAvailable - For propertyId: + " 523 + VehiclePropertyIds.toString(propertyId) + ", areaId: " 524 + Integer.toHexString(areaId) + ", no matching area ID found for HVAC_POWER_ON."); 525 return false; 526 } 527 registerHvacPropertyEventListeners()528 private void registerHvacPropertyEventListeners() { 529 for (int i = 0; i < HVAC_PROPERTIES.length; i++) { 530 @HvacProperty Integer propertyId = HVAC_PROPERTIES[i]; 531 if (mCarPropertyManager.getCarPropertyConfig(propertyId) == null) { 532 Log.w(TAG, "registerHvacPropertyEventListeners - propertyId: + " 533 + VehiclePropertyIds.toString(propertyId) + " is not implemented." 534 + " Skipping registering callback."); 535 continue; 536 } 537 mCarPropertyManager.registerCallback(mPropertyEventCallback, propertyId, 538 CarPropertyManager.SENSOR_RATE_ONCHANGE); 539 } 540 } 541 addHvacViewToMap(@vacProperty int propId, @AreaId int areaId, HvacView v)542 private void addHvacViewToMap(@HvacProperty int propId, @AreaId int areaId, 543 HvacView v) { 544 synchronized (mHvacPropertyViewMap) { 545 mHvacPropertyViewMap.computeIfAbsent(propId, k -> new HashMap<>()) 546 .computeIfAbsent(areaId, k -> new ArrayList<>()) 547 .add(v); 548 } 549 } 550 removeHvacViewFromMap(@vacProperty int propId, @AreaId int areaId, HvacView v)551 private void removeHvacViewFromMap(@HvacProperty int propId, @AreaId int areaId, HvacView v) { 552 synchronized (mHvacPropertyViewMap) { 553 Map<Integer, List<HvacView>> viewsRegisteredForProp = mHvacPropertyViewMap.get(propId); 554 if (viewsRegisteredForProp != null) { 555 List<HvacView> registeredViews = viewsRegisteredForProp.get(areaId); 556 if (registeredViews != null) { 557 registeredViews.remove(v); 558 if (registeredViews.isEmpty()) { 559 viewsRegisteredForProp.remove(areaId); 560 if (viewsRegisteredForProp.isEmpty()) { 561 mHvacPropertyViewMap.remove(propId); 562 } 563 } 564 } 565 } 566 } 567 } 568 }