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