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