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 }