1 /*
2  * Copyright (C) 2018 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.statusbar.car.hvac;
18 
19 import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
20 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
21 
22 import android.car.Car;
23 import android.car.Car.CarServiceLifecycleListener;
24 import android.car.VehicleUnit;
25 import android.car.hardware.CarPropertyValue;
26 import android.car.hardware.hvac.CarHvacManager;
27 import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
28 import android.content.Context;
29 import android.util.Log;
30 
31 import com.android.systemui.CarSystemUIFactory;
32 import com.android.systemui.SystemUIFactory;
33 
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 
41 /**
42  * Manages the connection to the Car service and delegates value changes to the registered
43  * {@link TemperatureView}s
44  */
45 public class HvacController {
46     public static final String TAG = "HvacController";
47 
48     private Context mContext;
49     private CarHvacManager mHvacManager;
50     private HashMap<HvacKey, List<TemperatureView>> mTempComponents = new HashMap<>();
51 
52     /**
53      * Callback for getting changes from {@link CarHvacManager} and setting the UI elements to
54      * match.
55      */
56     private final CarHvacEventCallback mHardwareCallback = new CarHvacEventCallback() {
57         @Override
58         public void onChangeEvent(final CarPropertyValue val) {
59             try {
60                 int areaId = val.getAreaId();
61                 int propertyId = val.getPropertyId();
62                 List<TemperatureView> temperatureViews = mTempComponents.get(
63                         new HvacKey(propertyId, areaId));
64                 if (temperatureViews != null && !temperatureViews.isEmpty()) {
65                     float value = (float) val.getValue();
66                     for (TemperatureView tempView : temperatureViews) {
67                         tempView.setTemp(value);
68                     }
69                 } // else the data is not of interest
70             } catch (Exception e) {
71                 // catch all so we don't take down the sysui if a new data type is
72                 // introduced.
73                 Log.e(TAG, "Failed handling hvac change event", e);
74             }
75         }
76 
77         @Override
78         public void onErrorEvent(final int propertyId, final int zone) {
79             Log.d(TAG, "HVAC error event, propertyId: " + propertyId
80                     + " zone: " + zone);
81         }
82     };
83 
84     private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
85         if (!ready) {
86             return;
87         }
88         try {
89             mHvacManager = (CarHvacManager) car.getCarManager(Car.HVAC_SERVICE);
90             mHvacManager.registerCallback(mHardwareCallback);
91             initComponents();
92         } catch (Exception e) {
93             Log.e(TAG, "Failed to correctly connect to HVAC", e);
94         }
95     };
96 
HvacController(Context context)97     public HvacController(Context context) {
98         mContext = context;
99     }
100 
101     /**
102      * Create connection to the Car service. Note: call backs from the Car service
103      * ({@link CarHvacManager}) will happen on the same thread this method was called from.
104      */
connectToCarService()105     public void connectToCarService() {
106         ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
107             .addListener(mCarServiceLifecycleListener);
108     }
109 
110     /**
111      * Add component to list and initialize it if the connection is up.
112      */
addHvacTextView(TemperatureView temperatureView)113     public void addHvacTextView(TemperatureView temperatureView) {
114 
115         HvacKey hvacKey = new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId());
116         if (!mTempComponents.containsKey(hvacKey)) {
117             mTempComponents.put(hvacKey, new ArrayList<>());
118         }
119         mTempComponents.get(hvacKey).add(temperatureView);
120         initComponent(temperatureView);
121     }
122 
initComponents()123     private void initComponents() {
124         Iterator<Map.Entry<HvacKey, List<TemperatureView>>> iterator =
125                 mTempComponents.entrySet().iterator();
126         while (iterator.hasNext()) {
127             Map.Entry<HvacKey, List<TemperatureView>> next = iterator.next();
128             List<TemperatureView> temperatureViews = next.getValue();
129             for (TemperatureView view : temperatureViews) {
130                 initComponent(view);
131             }
132         }
133     }
134 
initComponent(TemperatureView view)135     private void initComponent(TemperatureView view) {
136         int id = view.getPropertyId();
137         int zone = view.getAreaId();
138 
139         try {
140             if (mHvacManager != null
141                     && mHvacManager.isPropertyAvailable(HVAC_TEMPERATURE_DISPLAY_UNITS,
142                             VEHICLE_AREA_TYPE_GLOBAL)) {
143                 if (mHvacManager.getIntProperty(HVAC_TEMPERATURE_DISPLAY_UNITS,
144                         VEHICLE_AREA_TYPE_GLOBAL) == VehicleUnit.FAHRENHEIT) {
145                     view.setDisplayInFahrenheit(true);
146                 }
147 
148             }
149             if (mHvacManager == null || !mHvacManager.isPropertyAvailable(id, zone)) {
150                 view.setTemp(Float.NaN);
151                 return;
152             }
153             view.setTemp(mHvacManager.getFloatProperty(id, zone));
154         } catch (Exception e) {
155             view.setTemp(Float.NaN);
156             Log.e(TAG, "Failed to get value from hvac service", e);
157         }
158     }
159 
160     /**
161      * Removes all registered components. This is useful if you need to rebuild the UI since
162      * components self register.
163      */
removeAllComponents()164     public void removeAllComponents() {
165         mTempComponents.clear();
166     }
167 
168     /**
169      * Key for storing {@link TemperatureView}s in a hash map
170      */
171     private static class HvacKey {
172 
173         int mPropertyId;
174         int mAreaId;
175 
HvacKey(int propertyId, int areaId)176         private HvacKey(int propertyId, int areaId) {
177             mPropertyId = propertyId;
178             mAreaId = areaId;
179         }
180 
181         @Override
equals(Object o)182         public boolean equals(Object o) {
183             if (this == o) return true;
184             if (o == null || getClass() != o.getClass()) return false;
185             HvacKey hvacKey = (HvacKey) o;
186             return mPropertyId == hvacKey.mPropertyId
187                     && mAreaId == hvacKey.mAreaId;
188         }
189 
190         @Override
hashCode()191         public int hashCode() {
192             return Objects.hash(mPropertyId, mAreaId);
193         }
194     }
195 }
196