• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }