1 /* 2 * Copyright (C) 2024 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 android.car.hardware.property; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import static java.util.Objects.requireNonNull; 22 23 import android.annotation.CallbackExecutor; 24 import android.annotation.FlaggedApi; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SystemApi; 29 import android.car.Car; 30 import android.car.CarManagerBase; 31 import android.car.builtin.os.BuildHelper; 32 import android.car.feature.Flags; 33 import android.car.hardware.CarPropertyConfig; 34 import android.car.hardware.CarPropertyValue; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.util.Log; 38 39 import com.android.car.internal.ICarBase; 40 import com.android.car.internal.os.HandlerExecutor; 41 import com.android.car.internal.property.RawPropertyValue; 42 import com.android.car.internal.util.IntArray; 43 import com.android.internal.annotations.GuardedBy; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.lang.ref.WeakReference; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.concurrent.Executor; 50 51 /** 52 * This class provides APIs for recording and injecting vehicle properties for simulation 53 * purposes. This class is only available for userdebug and eng builds. 54 * 55 * <p>This class is used to record and inject vehicle property data. 56 * 57 * @hide 58 */ 59 @SystemApi 60 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 61 public final class CarPropertySimulationManager extends CarManagerBase { 62 63 private static final String TAG = CarPropertySimulationManager.class.getSimpleName(); 64 private final ICarProperty mCarPropertyService; 65 private final Object mLock = new Object(); 66 @GuardedBy("mLock") 67 private Executor mCallbackExecutor; 68 @GuardedBy("mLock") 69 private CarRecorderListener mListener; 70 private CarSubscriptionEventListenerToService mCarSubscriptionEventListenerToService = 71 new CarSubscriptionEventListenerToService(this); 72 73 private static class CarSubscriptionEventListenerToService extends 74 ICarPropertyEventListener.Stub { 75 private final WeakReference<CarPropertySimulationManager> mCarPropertySimulationManager; 76 CarSubscriptionEventListenerToService(CarPropertySimulationManager carPropertySimulationManager)77 CarSubscriptionEventListenerToService(CarPropertySimulationManager 78 carPropertySimulationManager) { 79 mCarPropertySimulationManager = new WeakReference<>(carPropertySimulationManager); 80 } 81 82 @Override onEvent(List<CarPropertyEvent> carPropertyEvents)83 public void onEvent(List<CarPropertyEvent> carPropertyEvents) throws RemoteException { 84 CarPropertySimulationManager carPropertySimulationManager = 85 mCarPropertySimulationManager.get(); 86 if (carPropertySimulationManager != null) { 87 carPropertySimulationManager.handleEvents(carPropertyEvents); 88 } 89 } 90 } 91 92 /** 93 * Get an instance of the CarPropertySimulationManager. 94 * 95 * <p>Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. 96 * 97 * @hide 98 */ CarPropertySimulationManager(ICarBase car, @NonNull IBinder service)99 public CarPropertySimulationManager(ICarBase car, @NonNull IBinder service) { 100 super(car); 101 mCarPropertyService = ICarProperty.Stub.asInterface(service); 102 } 103 104 /** @hide */ 105 @VisibleForTesting getCallbackExecutor()106 public Executor getCallbackExecutor() { 107 synchronized (mLock) { 108 return mCallbackExecutor; 109 } 110 } 111 112 /** @hide */ 113 @VisibleForTesting getCarRecorderListener()114 public CarRecorderListener getCarRecorderListener() { 115 synchronized (mLock) { 116 return mListener; 117 } 118 } 119 120 /** 121 * Initiates recording of vehicle properties. The recorded data can be used for playback with 122 * {@link CarPropertySimulationManager#injectVehicleProperties}. 123 * 124 * <p>This API is only available for userdebug and eng build. The caller must call 125 * {@link CarPropertySimulationManager#stopRecordingVehicleProperties} to stop this recording. 126 * 127 * <p>If the listener can no longer be reached (binder goes away) then the recording will be 128 * stopped. 129 * 130 * @param listener A listener to receive callbacks for hardware events. 131 * @param callbackExecutor The executor in which the callback is done on. If this is 132 * {@code null}, the callback will be executed on the event handler 133 * provided to the {@link android.car.Car} or the main thread if none 134 * was provided. 135 * 136 * @throws IllegalStateException If the build is not userdebug or eng. 137 * @throws IllegalStateException If there is a recording already in progress this includes one 138 * started by this process and started by other processes, only 139 * one system-wide recording is allowed at a single time. 140 * @throws IllegalStateException If vehicle injection mode is enabled. 141 * @throws SecurityException If missing permission. 142 * 143 * @return A list of {@link CarPropertyConfig} that are being recorded, the 144 * {@link CarPropertyConfig}'s {@code propertyId} will be of hardware property Id. 145 * 146 * @hide 147 */ 148 @SystemApi 149 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 150 @RequiresPermission(Car.PERMISSION_RECORD_VEHICLE_PROPERTIES) 151 @NonNull startRecordingVehicleProperties(@ullable @allbackExecutor Executor callbackExecutor, @NonNull CarRecorderListener listener)152 public List<CarPropertyConfig> startRecordingVehicleProperties(@Nullable @CallbackExecutor 153 Executor callbackExecutor, @NonNull CarRecorderListener listener) { 154 requireNonNull(listener); 155 synchronized (mLock) { 156 try { 157 // Binder call to registerRecordingListener is made with mLock held to maintain 158 // integrity with the internal state 159 List<CarPropertyConfig> configs = mCarPropertyService.registerRecordingListener( 160 mCarSubscriptionEventListenerToService).getConfigs(); 161 mListener = listener; 162 mCallbackExecutor = callbackExecutor; 163 if (mCallbackExecutor == null) { 164 mCallbackExecutor = new HandlerExecutor(getEventHandler()); 165 } 166 return configs; 167 } catch (RemoteException e) { 168 handleRemoteExceptionFromCarService(e); 169 } 170 return new ArrayList<>(); 171 } 172 } 173 174 /** 175 * Checks whether vehicle properties recording is in progress. 176 * 177 * @throws SecurityException If missing permission. 178 * @throws IllegalStateException If the build is not userdebug or eng. 179 * 180 * @return true if a recording is in progress, false otherwise. 181 * 182 * @hide 183 */ 184 @SystemApi 185 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 186 @RequiresPermission(Car.PERMISSION_RECORD_VEHICLE_PROPERTIES) isRecordingVehicleProperties()187 public boolean isRecordingVehicleProperties() { 188 try { 189 return mCarPropertyService.isRecordingVehicleProperties(); 190 } catch (RemoteException e) { 191 return handleRemoteExceptionFromCarService(e, false); 192 } 193 } 194 195 /** 196 * Stops recording of vehicle properties. 197 * 198 * <p>This method is idempotent. If the vehicle property recording is already 199 * disabled, calling this method has no effect. 200 * 201 * @throws IllegalStateException If the build is not userdebug or eng. 202 * @throws IllegalStateException If the recording that was started was not started by this 203 * process. 204 * @throws SecurityException If missing permission. 205 * 206 * @hide 207 */ 208 @SystemApi 209 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 210 @RequiresPermission(Car.PERMISSION_RECORD_VEHICLE_PROPERTIES) stopRecordingVehicleProperties()211 public void stopRecordingVehicleProperties() { 212 try { 213 mCarPropertyService.stopRecordingVehicleProperties( 214 mCarSubscriptionEventListenerToService); 215 } catch (RemoteException e) { 216 handleRemoteExceptionFromCarService(e); 217 return; 218 } 219 CarRecorderListener listener; 220 Executor executor; 221 synchronized (mLock) { 222 listener = mListener; 223 executor = mCallbackExecutor; 224 mListener = null; 225 mCallbackExecutor = null; 226 } 227 if (listener != null && executor != null) { 228 executor.execute(() -> listener.onRecordingFinished()); 229 } 230 } 231 232 /** 233 * Initializes vehicle property injection mode, when this is enabled properties not in 234 * {@code propertyIdsFromRealHardware} will not receive hardware events. To inject a vehicle 235 * property see {@link CarPropertySimulationManager#injectVehicleProperties}. 236 * 237 * <p>This method is system-wide. 238 * 239 * <p>This method is idempotent. If the vehicle property injection is already 240 * enabled, calling this method has no effect. 241 * 242 * @param propertyIdsFromRealHardware The propertyIds allowed to receive events from real 243 * hardware. If the propertyId is not supported by the real 244 * hardware, it will be ignored. 245 * 246 * @throws IllegalStateException If the build is not userdebug or eng. 247 * @throws IllegalStateException If car service is unable to enable injection mode. 248 * @throws SecurityException If missing permission. 249 * 250 * @return The elapsedRealtimeNanos when the injection mode has started. 251 * 252 * @hide 253 */ 254 @SystemApi 255 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 256 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) enableInjectionMode(@onNull List<Integer> propertyIdsFromRealHardware)257 public long enableInjectionMode(@NonNull List<Integer> propertyIdsFromRealHardware) { 258 requireNonNull(propertyIdsFromRealHardware); 259 IntArray propertyIdsFromRealHardwareArray = new IntArray(); 260 for (int i = 0; i < propertyIdsFromRealHardware.size(); i++) { 261 propertyIdsFromRealHardwareArray.add(propertyIdsFromRealHardware.get(i)); 262 } 263 try { 264 return mCarPropertyService.enableInjectionMode(propertyIdsFromRealHardwareArray 265 .toArray()); 266 } catch (RemoteException e) { 267 throw new IllegalStateException("Unable to enable injection mode."); 268 } 269 } 270 271 /** 272 * Disables vehicle property injection mode. See 273 * {@link CarPropertySimulationManager#enableInjectionMode} 274 * 275 * <p>This method is system-wide. 276 * 277 * <p>This method is idempotent. If the vehicle property injection is already 278 * disabled, calling this method has no effect. 279 * 280 * @throws IllegalStateException if the build is not userdebug or eng. 281 * @throws SecurityException If missing permission. 282 * 283 * @hide 284 */ 285 @SystemApi 286 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 287 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) disableInjectionMode()288 public void disableInjectionMode() { 289 try { 290 mCarPropertyService.disableInjectionMode(); 291 } catch (RemoteException e) { 292 handleRemoteExceptionFromCarService(e); 293 } 294 } 295 296 /** 297 * Gets the vehicle property injection mode. See 298 * {@link CarPropertySimulationManager#enableInjectionMode} 299 * 300 * @throws IllegalStateException if the build is not userdebug or eng. 301 * @throws SecurityException If missing permission. 302 * 303 * @return True if propertyInjectionMode is enabled False otherwise. 304 * 305 * @hide 306 */ 307 @SystemApi 308 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 309 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) isVehiclePropertyInjectionModeEnabled()310 public boolean isVehiclePropertyInjectionModeEnabled() { 311 try { 312 return mCarPropertyService.isVehiclePropertyInjectionModeEnabled(); 313 } catch (RemoteException e) { 314 return handleRemoteExceptionFromCarService(e, false); 315 } 316 } 317 318 /** 319 * Returns the latest {@link CarPropertyValue} that has been injected for the given propertyId. 320 * 321 * <p>**Note:** Due to potential concurrency, it is possible that a newer value has been 322 * injected since the retrieval of this {@link CarPropertyValue}. 323 * 324 * <p>This method returns null if no previous vehicle property injection has occurred for the 325 * specified propertyId. 326 * 327 * <p>Calling {@link CarPropertySimulationManager#disableInjectionMode} clears the last 328 * injected property value. 329 * 330 * @throws IllegalStateException if the build is not userdebug or eng. 331 * @throws IllegalStateException if vehiclePropertyInjection mode is not enabled. 332 * @throws SecurityException If missing permission. 333 * 334 * @return The latest CarPropertyValue that has been injected for the given PropertyId. 335 * 336 * @hide 337 */ 338 @SystemApi 339 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 340 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) 341 @Nullable getLastInjectedVehicleProperty(int propertyId)342 public CarPropertyValue getLastInjectedVehicleProperty(int propertyId) { 343 try { 344 return mCarPropertyService.getLastInjectedVehicleProperty(propertyId); 345 } catch (RemoteException e) { 346 return handleRemoteExceptionFromCarService(e, null); 347 } 348 } 349 350 /** 351 * Injects fake hardware data into the hardware. It will call the onPropertyEvent callback 352 * in the hardware. If the propertyId is part of the propertyIdsFromRealHardware when 353 * {@code enableInjectionMode} was called, those properties will also be ignored and will 354 * not be injected. The {@code mTimestampNanos} field in each {@link CarPropertyValue} 355 * represents the time elapsed since the initial call to 356 * {@link CarPropertySimulationManager#enableInjectionMode}. This elapsed time determines 357 * when the corresponding value is injected into the hardware. 358 * 359 * <p>This method supports queuing multiple injections. Each injection will be processed 360 * independently at its designated time, ensuring that subsequent injections do not override 361 * previous ones. 362 * 363 * <p>If {@code disableInjectionMode} is called before all scheduled property injections have 364 * occurred, any pending injections will be cancelled. 365 * 366 * <p>If any of the {@link CarPropertyValue} that are being injected are not valid, then none 367 * of the {@link CarPropertyValue} in {@code carPropertyValues} will be injected. 368 * 369 * @param carPropertyValues A list of carPropertyValues to inject. The hardware will inject the 370 * vehiclePropValue when the has reached elapsed timestamp in ns. If 371 * the timestamp has passed, it will inject the value immediately in 372 * increasing order. If this has no value, it will be treated as a 373 * no-op. 374 * 375 * @throws IllegalStateException if the build is not userdebug or eng. 376 * @throws IllegalStateException if vehiclePropertyInjectionMode is not enabled. 377 * @throws IllegalArgumentException If a {@link CarPropertyValue} that is being injected is out 378 * of range or the propertyId or areaId is invalid. 379 * @throws SecurityException If missing permission. 380 * 381 * @hide 382 */ 383 @SystemApi 384 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 385 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) injectVehicleProperties(@onNull List<CarPropertyValue> carPropertyValues)386 public void injectVehicleProperties(@NonNull List<CarPropertyValue> carPropertyValues) { 387 requireNonNull(carPropertyValues); 388 if (carPropertyValues.isEmpty()) { 389 return; 390 } 391 try { 392 mCarPropertyService.injectVehicleProperties(carPropertyValues); 393 } catch (RemoteException e) { 394 handleRemoteExceptionFromCarService(e); 395 } 396 } 397 398 /** 399 * Creates a {@link CarPropertyValue} object. 400 * 401 * <p>This method is used to construct {@link CarPropertyValue} objects for use with 402 * {@link CarPropertySimulationManager#injectVehicleProperties}. 403 * 404 * @param propertyId The property ID to be injected. 405 * @param areaId The area ID of the property, or {@code 0} if global. 406 * @param timestampNanos The timestamp of the property value in nanoseconds. This timestamp 407 * represents the elapsed time since the initial call to 408 * {@link CarPropertySimulationManager#injectVehicleProperties}. 409 * @param value The value of the property. 410 * @param <T> The type of the property value. 411 * 412 * @return A {@link CarPropertyValue} object with the specified parameters. 413 * 414 * @throws SecurityException If missing permission. 415 * @throws IllegalStateException If the build is not userdebug or eng. 416 * 417 * @hide 418 */ 419 @SystemApi 420 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 421 @RequiresPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) 422 @NonNull createCarPropertyValue(int propertyId, int areaId, @CarPropertyValue.PropertyStatus int status, long timestampNanos, @NonNull T value)423 public <T> CarPropertyValue<T> createCarPropertyValue(int propertyId, int areaId, 424 @CarPropertyValue.PropertyStatus int status, long timestampNanos, @NonNull T value) { 425 requireNonNull(value); 426 if (getContext().checkCallingOrSelfPermission(Car.PERMISSION_INJECT_VEHICLE_PROPERTIES) 427 != PERMISSION_GRANTED) { 428 throw new SecurityException("requires " + Car.PERMISSION_INJECT_VEHICLE_PROPERTIES); 429 } 430 if (!BuildHelper.isDebuggableBuild()) { 431 throw new IllegalStateException("not eng or user-debug build"); 432 } 433 return new CarPropertyValue<>(propertyId, areaId, status, timestampNanos, 434 new RawPropertyValue(value)); 435 } 436 437 /** @hide */ 438 @Override onCarDisconnected()439 protected void onCarDisconnected() { 440 // Not yet implemented 441 } 442 handleEvents(List<CarPropertyEvent> carPropertyEvents)443 private void handleEvents(List<CarPropertyEvent> carPropertyEvents) { 444 List<CarPropertyValue<?>> carPropertyValues = new ArrayList<>(); 445 for (int i = 0; i < carPropertyEvents.size(); i++) { 446 carPropertyValues.add(carPropertyEvents.get(i).getCarPropertyValue()); 447 } 448 Executor executor; 449 CarRecorderListener listener; 450 synchronized (mLock) { 451 if (mListener == null || mCallbackExecutor == null) { 452 Log.w(TAG, "Listener or callback executor was null"); 453 return; 454 } 455 executor = mCallbackExecutor; 456 listener = mListener; 457 } 458 executor.execute(() -> listener.onCarPropertyEvents(carPropertyValues)); 459 } 460 461 /** 462 * Applications registers CarRecorderListener object to receive updates on subscribed hardware 463 * data. 464 * 465 * @hide 466 */ 467 @SystemApi 468 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) 469 public interface CarRecorderListener { 470 /** 471 * Notifies client of events that have occurred. Notifies client every 100 events or 472 * delivers all remaining events if fewer than 100 remain when recording stopped. 473 * 474 * @param carPropertyValues A List of carPropertyValues, the carPropertyValues will be 475 * sorted in terms of increasing timestamps. 476 * 477 * @hide 478 */ 479 @SystemApi 480 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) onCarPropertyEvents(@onNull List<CarPropertyValue<?>> carPropertyValues)481 void onCarPropertyEvents(@NonNull List<CarPropertyValue<?>> carPropertyValues); 482 483 /** 484 * When stop recording has been called, this will notify client that the last event has 485 * occurred. 486 * 487 * @hide 488 */ 489 @SystemApi 490 @FlaggedApi(Flags.FLAG_CAR_PROPERTY_SIMULATION) onRecordingFinished()491 void onRecordingFinished(); 492 } 493 } 494