• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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