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.car.hal;
18 
19 import static android.car.VehiclePropertyIds.CLUSTER_DISPLAY_STATE;
20 import static android.car.VehiclePropertyIds.CLUSTER_NAVIGATION_STATE;
21 import static android.car.VehiclePropertyIds.CLUSTER_REPORT_STATE;
22 import static android.car.VehiclePropertyIds.CLUSTER_REQUEST_DISPLAY;
23 import static android.car.VehiclePropertyIds.CLUSTER_SWITCH_UI;
24 
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 
27 import android.annotation.NonNull;
28 import android.graphics.Insets;
29 import android.graphics.Rect;
30 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
31 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
32 import android.os.ServiceSpecificException;
33 import android.os.SystemClock;
34 import android.util.IntArray;
35 
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.server.utils.Slogf;
39 
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.LinkedList;
44 import java.util.List;
45 
46 /**
47  * Translates HAL input events to higher-level semantic information.
48  */
49 public final class ClusterHalService extends HalServiceBase {
50     private static final String TAG = ClusterHalService.class.getSimpleName();
51     private static final boolean DBG = false;
52     public static final int DISPLAY_OFF = 0;
53     public static final int DISPLAY_ON = 1;
54     public static final int DONT_CARE = -1;
55 
56     /**
57      * Interface to receive incoming Cluster HAL events.
58      */
59     public interface ClusterHalEventCallback {
60         /**
61          * Called when CLUSTER_SWITCH_UI message is received.
62          *
63          * @param uiType uiType ClusterOS wants to switch to
64          */
onSwitchUi(int uiType)65         void onSwitchUi(int uiType);
66 
67         /**
68          * Called when CLUSTER_DISPLAY_STATE message is received.
69          *
70          * @param onOff  0 - off, 1 - on
71          * @param bounds the area to render the cluster Activity in pixel
72          * @param insets Insets of the cluster display
73          */
onDisplayState(int onOff, Rect bounds, Insets insets)74         void onDisplayState(int onOff, Rect bounds, Insets insets);
75     }
76 
77     ;
78 
79     private static final int[] SUPPORTED_PROPERTIES = new int[]{
80             CLUSTER_SWITCH_UI,
81             CLUSTER_DISPLAY_STATE,
82             CLUSTER_REPORT_STATE,
83             CLUSTER_REQUEST_DISPLAY,
84             CLUSTER_NAVIGATION_STATE,
85     };
86 
87     private static final int[] CORE_PROPERTIES = new int[]{
88             CLUSTER_SWITCH_UI,
89             CLUSTER_REPORT_STATE,
90             CLUSTER_DISPLAY_STATE,
91             CLUSTER_REQUEST_DISPLAY,
92     };
93 
94     private static final int[] SUBSCRIBABLE_PROPERTIES = new int[]{
95             CLUSTER_SWITCH_UI,
96             CLUSTER_DISPLAY_STATE,
97     };
98 
99     private final Object mLock = new Object();
100 
101     @GuardedBy("mLock")
102     private ClusterHalEventCallback mCallback;
103 
104     private final VehicleHal mHal;
105 
106     private volatile boolean mIsCoreSupported;
107     private volatile boolean mIsNavigationStateSupported;
108 
ClusterHalService(VehicleHal hal)109     public ClusterHalService(VehicleHal hal) {
110         mHal = hal;
111     }
112 
113     @Override
init()114     public void init() {
115         Slogf.d(TAG, "initClusterHalService");
116         if (!isCoreSupported()) return;
117 
118         for (int property : SUBSCRIBABLE_PROPERTIES) {
119             mHal.subscribeProperty(this, property);
120         }
121     }
122 
123     @Override
release()124     public void release() {
125         Slogf.d(TAG, "releaseClusterHalService");
126         synchronized (mLock) {
127             mCallback = null;
128         }
129     }
130 
131     /**
132      * Sets the event callback to receive Cluster HAL events.
133      */
setCallback(ClusterHalEventCallback callback)134     public void setCallback(ClusterHalEventCallback callback) {
135         LinkedList<VehiclePropValue> eventsToDispatch = null;
136         synchronized (mLock) {
137             mCallback = callback;
138         }
139     }
140 
141     @NonNull
142     @Override
getAllSupportedProperties()143     public int[] getAllSupportedProperties() {
144         return SUPPORTED_PROPERTIES;
145     }
146 
147     @Override
takeProperties(@onNull Collection<VehiclePropConfig> properties)148     public void takeProperties(@NonNull Collection<VehiclePropConfig> properties) {
149         IntArray supportedProperties = new IntArray(properties.size());
150         for (VehiclePropConfig property : properties) {
151             supportedProperties.add(property.prop);
152         }
153         mIsCoreSupported = true;
154         for (int coreProperty : CORE_PROPERTIES) {
155             if (supportedProperties.indexOf(coreProperty) < 0) {
156                 mIsCoreSupported = false;
157                 break;
158             }
159         }
160         mIsNavigationStateSupported = supportedProperties.indexOf(CLUSTER_NAVIGATION_STATE) >= 0;
161         Slogf.d(TAG, "takeProperties: coreSupported=%s, navigationStateSupported=%s",
162                 mIsCoreSupported, mIsNavigationStateSupported);
163     }
164 
isCoreSupported()165     public boolean isCoreSupported() {
166         return mIsCoreSupported;
167     }
168 
isNavigationStateSupported()169     public boolean isNavigationStateSupported() {
170         return mIsNavigationStateSupported;
171     }
172 
173     @Override
onHalEvents(List<VehiclePropValue> values)174     public void onHalEvents(List<VehiclePropValue> values) {
175         Slogf.d(TAG, "handleHalEvents(): %s", values);
176         ClusterHalEventCallback callback;
177         synchronized (mLock) {
178             callback = mCallback;
179         }
180         if (callback == null || !isCoreSupported()) return;
181 
182         for (VehiclePropValue value : values) {
183             switch (value.prop) {
184                 case CLUSTER_SWITCH_UI:
185                     int uiType = value.value.int32Values.get(0);
186                     callback.onSwitchUi(uiType);
187                     break;
188                 case CLUSTER_DISPLAY_STATE:
189                     int onOff = value.value.int32Values.get(0);
190                     Rect bounds = null;
191                     if (hasNoDontCare(value.value.int32Values, 1, 4, "bounds")) {
192                         bounds = new Rect(
193                                 value.value.int32Values.get(1), value.value.int32Values.get(2),
194                                 value.value.int32Values.get(3), value.value.int32Values.get(4));
195                     }
196                     Insets insets = null;
197                     if (hasNoDontCare(value.value.int32Values, 5, 4, "insets")) {
198                         insets = Insets.of(
199                                 value.value.int32Values.get(5), value.value.int32Values.get(6),
200                                 value.value.int32Values.get(7), value.value.int32Values.get(8));
201                     }
202                     callback.onDisplayState(onOff, bounds, insets);
203                     break;
204                 default:
205                     Slogf.w(TAG, "received unsupported event from HAL: %s", value);
206             }
207         }
208     }
209 
hasNoDontCare(ArrayList<Integer> values, int start, int length, String fieldName)210     private static boolean hasNoDontCare(ArrayList<Integer> values, int start, int length,
211             String fieldName) {
212         int count = 0;
213         for (int i = start; i < start + length; ++i) {
214             if (values.get(i) == DONT_CARE) ++count;
215         }
216         if (count == 0) return true;
217         if (count != length) {
218             Slogf.w(TAG, "Don't care should be set in the whole %s.", fieldName);
219         }
220         return false;
221     }
222 
223     /**
224      * Reports the current display state and ClusterUI state.
225      *
226      * @param onOff          0 - off, 1 - on
227      * @param bounds         the area to render the cluster Activity in pixel
228      * @param insets         Insets of the cluster display
229      * @param uiTypeMain     uiType that ClusterHome tries to show in main area
230      * @param uiTypeSub      uiType that ClusterHome tries to show in sub area
231      * @param uiAvailability the byte array to represent the availability of ClusterUI.
232      */
reportState(int onOff, Rect bounds, Insets insets, int uiTypeMain, int uiTypeSub, byte[] uiAvailability)233     public void reportState(int onOff, Rect bounds, Insets insets,
234             int uiTypeMain, int uiTypeSub, byte[] uiAvailability) {
235         if (!isCoreSupported()) return;
236         VehiclePropValue request = createVehiclePropValue(CLUSTER_REPORT_STATE);
237         request.value.int32Values.add(onOff);
238         request.value.int32Values.add(bounds.left);
239         request.value.int32Values.add(bounds.top);
240         request.value.int32Values.add(bounds.right);
241         request.value.int32Values.add(bounds.bottom);
242         request.value.int32Values.add(insets.left);
243         request.value.int32Values.add(insets.top);
244         request.value.int32Values.add(insets.right);
245         request.value.int32Values.add(insets.bottom);
246         request.value.int32Values.add(uiTypeMain);
247         request.value.int32Values.add(uiTypeSub);
248         fillByteList(request.value.bytes, uiAvailability);
249         send(request);
250     }
251 
252     /**
253      * Requests to turn the cluster display on to show some ClusterUI.
254      *
255      * @param uiType uiType that ClusterHome tries to show in main area
256      */
requestDisplay(int uiType)257     public void requestDisplay(int uiType) {
258         if (!isCoreSupported()) return;
259         VehiclePropValue request = createVehiclePropValue(CLUSTER_REQUEST_DISPLAY);
260         request.value.int32Values.add(uiType);
261         send(request);
262     }
263 
264 
265     /**
266      * Informs the current navigation state.
267      *
268      * @param navigateState the serialized message of {@code NavigationStateProto}
269      */
sendNavigationState(byte[] navigateState)270     public void sendNavigationState(byte[] navigateState) {
271         if (!isNavigationStateSupported()) return;
272         VehiclePropValue request = createVehiclePropValue(CLUSTER_NAVIGATION_STATE);
273         fillByteList(request.value.bytes, navigateState);
274         send(request);
275     }
276 
send(VehiclePropValue request)277     private void send(VehiclePropValue request) {
278         try {
279             mHal.set(request);
280         } catch (ServiceSpecificException e) {
281             Slogf.e(TAG, "Failed to send request: " + request, e);
282         }
283     }
284 
fillByteList(ArrayList<Byte> byteList, byte[] bytesArray)285     private static void fillByteList(ArrayList<Byte> byteList, byte[] bytesArray) {
286         byteList.ensureCapacity(bytesArray.length);
287         for (byte b : bytesArray) {
288             byteList.add(b);
289         }
290     }
291 
createVehiclePropValue(int property)292     private static VehiclePropValue createVehiclePropValue(int property) {
293         VehiclePropValue value = new VehiclePropValue();
294         value.prop = property;
295         value.timestamp = SystemClock.elapsedRealtime();
296         return value;
297     }
298 
299     @Override
300     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)301     public void dump(PrintWriter writer) {
302         writer.println("*Cluster HAL*");
303         writer.println("mIsCoreSupported:" + isCoreSupported());
304         writer.println("mIsNavigationStateSupported:" + isNavigationStateSupported());
305     }
306 }
307