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.evs;
18 
19 import static android.car.evs.CarEvsManager.ERROR_NONE;
20 import static android.car.evs.CarEvsManager.ERROR_UNAVAILABLE;
21 
22 import static com.android.car.CarLog.TAG_EVS;
23 import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_NORMAL;
24 import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_HIGH;
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.car.Car;
30 import android.car.VehiclePropertyIds;
31 import android.car.builtin.content.pm.PackageManagerHelper;
32 import android.car.builtin.os.BuildHelper;
33 import android.car.builtin.util.Slogf;
34 import android.car.evs.CarEvsBufferDescriptor;
35 import android.car.evs.CarEvsManager;
36 import android.car.evs.CarEvsManager.CarEvsError;
37 import android.car.evs.CarEvsManager.CarEvsServiceState;
38 import android.car.evs.CarEvsManager.CarEvsServiceType;
39 import android.car.evs.CarEvsStatus;
40 import android.car.evs.ICarEvsStatusListener;
41 import android.car.evs.ICarEvsStreamCallback;
42 import android.car.hardware.CarPropertyValue;
43 import android.car.hardware.property.CarPropertyEvent;
44 import android.car.hardware.property.ICarPropertyEventListener;
45 import android.content.ComponentName;
46 import android.content.Context;
47 import android.content.pm.PackageManager.NameNotFoundException;
48 import android.hardware.automotive.vehicle.VehicleGear;
49 import android.hardware.display.DisplayManager;
50 import android.hardware.display.DisplayManager.DisplayListener;
51 import android.os.Binder;
52 import android.os.Handler;
53 import android.os.IBinder;
54 import android.os.Looper;
55 import android.os.RemoteCallbackList;
56 import android.os.RemoteException;
57 import android.os.ServiceManager;
58 import android.os.SystemClock;
59 import android.os.UserHandle;
60 import android.util.ArrayMap;
61 import android.util.ArraySet;
62 import android.util.Log;
63 import android.util.SparseArray;
64 import android.util.proto.ProtoOutputStream;
65 import android.view.Display;
66 
67 import com.android.car.CarPropertyService;
68 import com.android.car.CarServiceBase;
69 import com.android.car.CarServiceUtils;
70 import com.android.car.R;
71 import com.android.car.hal.EvsHalService;
72 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
73 import com.android.car.internal.evs.CarEvsUtils;
74 import com.android.car.internal.util.IndentingPrintWriter;
75 import com.android.internal.annotations.GuardedBy;
76 import com.android.internal.annotations.VisibleForTesting;
77 
78 import java.lang.ref.WeakReference;
79 import java.util.List;
80 import java.util.Objects;
81 import java.util.Set;
82 
83 /**
84  * A service that listens to the Extended View System across a HAL boundary and exposes the data to
85  * system clients in Android via {@link android.car.evs.CarEvsManager}.
86  *
87  * Because of Fast Message Queue usages, android.hardware.automotive.evs@1.1 interfaces does not
88  * support Java backend and, therefore, actual API calls are done in native methods.
89  *
90  *
91  * CarEvsService consists of four states:
92  *
93  * UNAVAILABLE: CarEvsService is not connected to the Extended View System service.  In this
94  * state, any service request will be declined.
95  *
96  * INACTIVE: CarEvsService has a valid, live connection the Extended View System service and
97  * ready for any service requests.
98  *
99  * REQUESTED: CarEvsService received a service requeste from a privileged client and requested
100  * the System UI to launch the camera viewing activity.
101  *
102  * ACTIVE: CarEvsService is actively streaming a video to the client.
103  *
104  * See CarEvsService.StateMachine class for more details.
105  */
106 public final class CarEvsService extends android.car.evs.ICarEvsService.Stub
107         implements CarServiceBase {
108 
109     private static final boolean DBG = Slogf.isLoggable(TAG_EVS, Log.DEBUG);
110     private static final String EVS_INTERFACE_NAME =
111             "android.hardware.automotive.evs.IEvsEnumerator";
112     private static final String EVS_DEFAULT_INSTANCE_NAME = "default";
113 
114 
115     static final class EvsHalEvent {
116         private long mTimestamp;
117         private int mServiceType;
118         private boolean mOn;
119 
EvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on)120         public EvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) {
121             mTimestamp = timestamp;
122             mServiceType = type;
123             mOn = on;
124         }
125 
getTimestamp()126         public long getTimestamp() {
127             return mTimestamp;
128         }
129 
getServiceType()130         public @CarEvsServiceType int getServiceType() {
131             return mServiceType;
132         }
133 
isRequestingToStartActivity()134         public boolean isRequestingToStartActivity() {
135             return mOn;
136         }
137 
138         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()139         public String toString() {
140             return "ServiceType=" + CarEvsUtils.convertToString(mServiceType) +
141                     ", mOn=" + mOn + ", Timestamp=" + mTimestamp;
142         }
143     }
144 
145     private final Context mContext;
146     private final Context mBuiltinContext;
147     private final EvsHalService mEvsHalService;
148     private final CarPropertyService mPropertyService;
149     private final DisplayManager mDisplayManager;  // To monitor the default display's state
150     private final Object mLock = new Object();
151     private final ArraySet<IBinder> mSessionTokens = new ArraySet<>();
152     private final boolean mIsEvsAvailable;
153 
154     // This handler is to monitor the client sends a video stream request within a given time
155     // after a state transition to the REQUESTED state.
156     private final Handler mHandler = new Handler(Looper.getMainLooper());
157 
158     private static final class StatusListenerList
159             extends RemoteCallbackList<ICarEvsStatusListener> {
160         private final WeakReference<CarEvsService> mService;
161 
StatusListenerList(CarEvsService evsService)162         StatusListenerList(CarEvsService evsService) {
163             mService = new WeakReference<>(evsService);
164         }
165 
166         /** Handle callback death */
167         @Override
onCallbackDied(ICarEvsStatusListener listener)168         public void onCallbackDied(ICarEvsStatusListener listener) {
169             Slogf.w(TAG_EVS, "StatusListener has died: " + listener.asBinder());
170 
171             CarEvsService svc = mService.get();
172             if (svc != null) {
173                 svc.handleClientDisconnected(listener);
174             }
175         }
176     }
177 
178     private final StatusListenerList mStatusListeners = new StatusListenerList(this);
179 
180     /**
181      * {@link CarPropertyEvent} listener registered with {@link CarPropertyService} to listen to
182      * {@link VehicleProperty.GEAR_SELECTION} change notifications.
183      */
184     private final ICarPropertyEventListener mGearSelectionPropertyListener =
185             new ICarPropertyEventListener.Stub() {
186                 @Override
187                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
188                     if (events.isEmpty()) {
189                         return;
190                     }
191 
192                     // Handle only the latest event
193                     Slogf.i(TAG_EVS, "Handling GearSelection event");
194                     handlePropertyEvent(events.get(events.size() - 1));
195                 }
196             };
197 
198     private final DisplayListener mDisplayListener = new DisplayListener() {
199             @Override
200             public void onDisplayAdded(int displayId) {
201                 // Nothing to do
202             }
203 
204             @Override
205             public void onDisplayRemoved(int displayId) {
206                 // Nothing to do
207             }
208 
209             @Override
210             public void onDisplayChanged(int displayId) {
211                 if (displayId != Display.DEFAULT_DISPLAY) {
212                     // We are interested only in the default display.
213                     return;
214                 }
215 
216                 Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
217                 if (mCurrentDisplayState == display.getState()) {
218                     // We already handled this display state change.
219                     if (DBG) {
220                         Slogf.d(TAG_EVS, "We already handled a reported display status, %d",
221                                 display.getState());
222                     }
223                     return;
224                 }
225 
226                 // TODO(b/292155786): Current implementation is optimized for the device with a
227                 //                    single display and therefore we may need to consider the
228                 //                    source of the display event and start/stop activities
229                 //                    accordingly.
230                 switch (display.getState()) {
231                     case Display.STATE_ON:
232                         // Requests each StateMachine to launch a registered activity if it's
233                         // necessary.
234                         for (int i = 0; i < mServiceInstances.size(); i++) {
235                             if (mServiceInstances.valueAt(i)
236                                     .requestStartActivityIfNecessary() == ERROR_NONE) {
237                                 continue;
238                             }
239                             Slogf.e(TAG_EVS, "Failed to start %s's activity.",
240                                     CarEvsUtils.convertToString(mServiceInstances.keyAt(i)));
241                         }
242                         break;
243 
244                     case Display.STATE_OFF:
245                         // Each StateMachine stores a valid session token that was used for
246                         // recognizing a streaming callback from a launched activity.
247                         // CarEvsService will request each StateMachine to stop those callbacks
248                         // and let other callbacks continue running. Activities not launched by
249                         // CarEvsService must handle display's state changes properly by
250                         // themselves.
251                         for (int i = 0; i < mServiceInstances.size(); i++) {
252                             mServiceInstances.valueAt(i)
253                                     .requestStopActivity(REQUEST_PRIORITY_HIGH);
254                         }
255                         break;
256 
257                     default:
258                         // Nothing to do for all other state changes
259                         break;
260                 }
261 
262                 mCurrentDisplayState = display.getState();
263             }
264         };
265 
266     // Service instances per each type.
267     private final SparseArray<StateMachine> mServiceInstances;
268 
269     // Associates callback objects with their service types.
270     private final ArrayMap<IBinder, ArraySet<Integer>> mCallbackToServiceType =
271             new ArrayMap<>();
272 
273     // The latest display state we have processed.
274     private int mCurrentDisplayState = Display.STATE_OFF;
275 
276     // This boolean flag is true if CarEvsService uses GEAR_SELECTION VHAL property instead of
277     // EVS_SERVICE_REQUEST.
278     private boolean mUseGearSelection = true;
279 
280     // The last event EvsHalService reported.  This will be set to null when a related service
281     // request is handled.
282     //
283     // To properly handle a HAL event that occurred before CarEvsService is ready, we initialize
284     // mLastEvsHalEvent with a zero timestamp here.
285     @GuardedBy("mLock")
286     private EvsHalEvent mLastEvsHalEvent = new EvsHalEvent(/* timestamp= */ 0,
287             CarEvsManager.SERVICE_TYPE_REARVIEW, /* on= */ false);
288 
289     /** Creates an Extended View System service instance given a {@link Context}. */
CarEvsService(Context context, Context builtinContext, EvsHalService halService, CarPropertyService propertyService)290     public CarEvsService(Context context, Context builtinContext, EvsHalService halService,
291             CarPropertyService propertyService) {
292         this(context, builtinContext, halService, propertyService, /* checkDependencies= */ true);
293     }
294 
295     @VisibleForTesting
CarEvsService(Context context, Context builtinContext, EvsHalService halService, CarPropertyService propertyService, boolean checkDependencies)296     CarEvsService(Context context, Context builtinContext, EvsHalService halService,
297             CarPropertyService propertyService, boolean checkDependencies) {
298         mContext = context;
299         mBuiltinContext = builtinContext;
300 
301         // CarEvsService should become ineffective if the EVS service is not available. We confirm
302         // this by checking whether IEvsEnumerator/default instance is declared in VINTF. This check
303         // could be skipped only for testing purposes.
304         String instanceName = EVS_INTERFACE_NAME + "/" + EVS_DEFAULT_INSTANCE_NAME;
305         mIsEvsAvailable = !checkDependencies || ServiceManager.isDeclared(instanceName);
306         if (!mIsEvsAvailable) {
307             Slogf.e(TAG_EVS, "%s does not exist. CarEvsService won't be available.", instanceName);
308 
309             // Set all final variables ineffective.
310             mPropertyService = null;
311             mEvsHalService = null;
312             mServiceInstances = new SparseArray<>();
313             mDisplayManager = null;
314 
315             return;
316         }
317 
318         mPropertyService = propertyService;
319         mEvsHalService = halService;
320 
321         // Reads the service configuration and initializes service instances.
322         String[] rawConfigurationStrings = mContext.getResources()
323                 .getStringArray(R.array.config_carEvsService);
324         if (rawConfigurationStrings != null && rawConfigurationStrings.length > 0) {
325             mServiceInstances = new SparseArray<>(rawConfigurationStrings.length);
326             for (String rawString : rawConfigurationStrings) {
327                 CarEvsServiceUtils.Parameters params = CarEvsServiceUtils.parse(rawString);
328 
329                 StateMachine s = new StateMachine(context, builtinContext, this,
330                         params.getActivityComponentName(), params.getType(), params.getCameraId());
331                 mServiceInstances.put(params.getType(), s);
332             }
333 
334             if (mServiceInstances.size() < 1) {
335                 Slogf.e(TAG_EVS, "No valid configuration has been found. " +
336                         "CarEvsService won't be available.");
337                 mDisplayManager = null;
338                 return;
339             }
340         } else {
341             mServiceInstances = new SparseArray<>(/* capacity= */ 1);
342             Slogf.i(TAG_EVS, "CarEvsService will be initialized only for the rearview service " +
343                              "because no service configuration was available via " +
344                              "config_carEvsService.");
345 
346             String activityName = mContext.getResources()
347                     .getString(R.string.config_evsCameraActivity);
348             ComponentName activityComponentName;
349             if (!activityName.isEmpty()) {
350                 activityComponentName = ComponentName.unflattenFromString(activityName);
351             } else {
352                 activityComponentName = null;
353             }
354             if (DBG) Slogf.d(TAG_EVS, "evsCameraActivity=" + activityName);
355 
356             String cameraId = context.getString(R.string.config_evsRearviewCameraId);
357             StateMachine s = new StateMachine(context, builtinContext, this, activityComponentName,
358                     CarEvsManager.SERVICE_TYPE_REARVIEW, cameraId);
359             mServiceInstances.put(CarEvsManager.SERVICE_TYPE_REARVIEW, s);
360         }
361 
362         mDisplayManager = context.getSystemService(DisplayManager.class);
363         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
364     }
365 
366     @VisibleForTesting
367     final class EvsTriggerListener implements EvsHalService.EvsHalEventListener {
368 
369         /** Implements EvsHalService.EvsHalEventListener to monitor VHAL properties. */
370         @Override
onEvent(@arEvsServiceType int type, boolean on)371         public void onEvent(@CarEvsServiceType int type, boolean on) {
372             if (DBG) {
373                 Slogf.d(TAG_EVS,
374                         "Received an event from EVS HAL: type = " + type + ", on = " + on);
375             }
376 
377             StateMachine instance = mServiceInstances.get(type);
378             if (instance == null) {
379                 Slogf.w(TAG_EVS, "CarEvsService is not configured for %s", type);
380                 return;
381             }
382 
383             // Stores the last event.
384             synchronized (mLock) {
385                 mLastEvsHalEvent = new EvsHalEvent(SystemClock.elapsedRealtimeNanos(), type, on);
386             }
387 
388             if (on) {
389                 // Request a camera activity.
390                 if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
391                     Slogf.e(TAG_EVS, "Fail to request a registered activity.");
392                 }
393             } else {
394                 // Stop a video stream and close an activity.
395                 if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
396                     Slogf.e(TAG_EVS, "Fail to stop a registered activity.");
397                 }
398             }
399         }
400     }
401 
402     @VisibleForTesting
403     final EvsTriggerListener mEvsTriggerListener = new EvsTriggerListener();
404 
405     @Override
init()406     public void init() {
407         if (DBG) {
408             Slogf.d(TAG_EVS, "Initializing the service");
409         }
410 
411         if (!mIsEvsAvailable) {
412             Slogf.e(TAG_EVS, "CarEvsService cannot be initialized due to missing dependencies.");
413             return;
414         }
415 
416         for (int i = mServiceInstances.size() - 1; i >= 0; i--) {
417             StateMachine instance = mServiceInstances.valueAt(i);
418             if (instance.init()) {
419                 continue;
420             }
421 
422             Slogf.e(TAG_EVS, "Failed to initialize a service handle for %s.",
423                     mServiceInstances.keyAt(i));
424             mServiceInstances.removeAt(i);
425         }
426 
427         if (mEvsHalService.isEvsServiceRequestSupported()) {
428             try {
429                 mEvsHalService.setListener(mEvsTriggerListener);
430                 if (DBG) {
431                     Slogf.d(TAG_EVS, "CarEvsService listens to EVS_SERVICE_REQUEST property.");
432                 }
433                 mUseGearSelection = false;
434             } catch (IllegalStateException e) {
435                 Slogf.w(TAG_EVS, "Failed to set a EvsHalService listener. Try to use "
436                         + "GEAR_SELECTION.");
437             }
438         }
439 
440         if (mUseGearSelection) {
441             if (mPropertyService == null || mPropertyService.getPropertySafe(
442                     VehiclePropertyIds.GEAR_SELECTION, /*areaId=*/ 0) == null) {
443                 Slogf.w(TAG_EVS,
444                         "GEAR_SELECTION property is also not available. " +
445                         "CarEvsService may not respond to the system events.");
446                 mUseGearSelection = false;
447             } else {
448                 if (DBG) {
449                     Slogf.d(TAG_EVS, "CarEvsService listens to GEAR_SELECTION property.");
450                 }
451 
452                 if (!mPropertyService.registerListenerSafe(
453                         VehiclePropertyIds.GEAR_SELECTION, /*updateRateHz=*/0,
454                         mGearSelectionPropertyListener)) {
455                     Slogf.w(TAG_EVS, "Failed to register a listener for GEAR_SELECTION property.");
456                     mUseGearSelection = false;
457                 }
458             }
459         }
460 
461         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
462         if (instance == null) {
463             Slogf.w(TAG_EVS, "The service is not initialized for the rearview service.");
464             return;
465         }
466 
467         instance.connectToHalServiceIfNecessary();
468     }
469 
470     @Override
release()471     public void release() {
472         if (DBG) {
473             Slogf.d(TAG_EVS, "Finalizing the service");
474         }
475 
476         mDisplayManager.unregisterDisplayListener(mDisplayListener);
477 
478         if (mUseGearSelection && mPropertyService != null) {
479             if (DBG) {
480                 Slogf.d(TAG_EVS, "Unregister a property listener in release()");
481             }
482             mPropertyService.unregisterListenerSafe(VehiclePropertyIds.GEAR_SELECTION,
483                     mGearSelectionPropertyListener);
484         }
485 
486         for (int i = 0; i < mServiceInstances.size(); i++) {
487             StateMachine instance = mServiceInstances.valueAt(i);
488             instance.release();
489         }
490 
491         mStatusListeners.kill();
492     }
493 
494     @Override
495     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)496     public void dump(IndentingPrintWriter writer) {
497         writer.println("*CarEvsService*");
498 
499         writer.increaseIndent();
500         for (int i = 0; i < mServiceInstances.size(); i++) {
501             mServiceInstances.valueAt(i).dump(writer);
502         }
503         writer.decreaseIndent();
504         writer.printf("\n");
505 
506         synchronized (mLock) {
507             writer.printf("%d service listeners subscribed.\n",
508                     mStatusListeners.getRegisteredCallbackCount());
509             writer.printf("Last HAL event: %s\n", mLastEvsHalEvent);
510         }
511     }
512 
513     @Override
514     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)515     public void dumpProto(ProtoOutputStream proto) {}
516 
517     /**
518      * Registers a {@link ICarEvsStatusListener} to listen requests to control the camera
519      * previewing activity.
520      *
521      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
522      * access.
523      *
524      * @param listener {@link ICarEvsStatusListener} listener to register.
525      */
526     @Override
registerStatusListener(@onNull ICarEvsStatusListener listener)527     public void registerStatusListener(@NonNull ICarEvsStatusListener listener) {
528         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
529         Objects.requireNonNull(listener);
530 
531         if (DBG) {
532             Slogf.d(TAG_EVS, "Registering a new service listener");
533         }
534         mStatusListeners.register(listener);
535     }
536 
537     /**
538      * Unregister the given {@link ICarEvsStatusListener} listener from receiving events.
539      *
540      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
541      * access.
542      *
543      * @param listener {@link ICarEvsStatusListener} listener to unregister.
544      */
545     @Override
unregisterStatusListener(@onNull ICarEvsStatusListener listener)546     public void unregisterStatusListener(@NonNull ICarEvsStatusListener listener) {
547         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
548         Objects.requireNonNull(listener);
549 
550         mStatusListeners.unregister(listener);
551     }
552 
553     /**
554      * Requests the system to start an activity to show the preview from a given EVS service type.
555      *
556      * <p>Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to
557      * access.
558      *
559      * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType}
560      * @return {@link android.car.evs.CarEvsManager#CarEvsError}
561      */
562     @Override
startActivity(int type)563     public @CarEvsError int startActivity(int type) {
564         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY);
565 
566         if (type == CarEvsManager.SERVICE_TYPE_SURROUNDVIEW) {
567             // TODO(b/179029031): Removes below when Surround View service is integrated.
568             Slogf.e(TAG_EVS, "Surround view is not supported yet.");
569             return ERROR_UNAVAILABLE;
570         }
571 
572         StateMachine instance = mServiceInstances.get(type);
573         if (instance == null) {
574             return ERROR_UNAVAILABLE;
575         }
576 
577         return instance.requestStartActivity(REQUEST_PRIORITY_NORMAL);
578     }
579 
580     /**
581      * Requests to stop a current previewing activity launched via {@link #startActivity}.
582      *
583      * <p>Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to
584      * access.
585      */
586     @Override
stopActivity()587     public void stopActivity() {
588         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY);
589 
590         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
591         if (instance == null) {
592             return;
593         }
594 
595         instance.requestStopActivity(REQUEST_PRIORITY_NORMAL);
596     }
597 
598     /**
599      * Starts a video stream.
600      *
601      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
602      *
603      * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType}
604      * @param token IBinder object as a session token.  If this is not null, CarEvsService handles a
605      *              coming client as a privileged client.
606      * @param callback {@link ICarEvsStreamCallback} listener to register.
607      * @return {@link android.car.evs.CarEvsManager.CarEvsError}
608      */
609     @Override
startVideoStream(@arEvsServiceType int type, @Nullable IBinder token, @NonNull ICarEvsStreamCallback callback)610     public @CarEvsError int startVideoStream(@CarEvsServiceType int type, @Nullable IBinder token,
611             @NonNull ICarEvsStreamCallback callback) {
612         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
613         Objects.requireNonNull(callback);
614 
615         StateMachine instance = mServiceInstances.get(type);
616         if (instance == null) {
617             Slogf.e(TAG_EVS, "CarEvsService is not configured for a service type %d.", type);
618             return ERROR_UNAVAILABLE;
619         }
620 
621         // Single client can subscribe to multiple services.
622         // ArrayMap<IBinder, ArraySet<Integer>>
623         // Remembers which service a given callback is subscribing to.
624         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
625         if (types == null) {
626             mCallbackToServiceType.put(callback.asBinder(),
627                     new ArraySet<>(Set.of(type)));
628         } else {
629             types.add(type);
630         }
631 
632         return instance.requestStartVideoStream(callback, token);
633     }
634 
635     /**
636      * Requests to stop a video stream from the current client.
637      *
638      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
639      *
640      * @param callback {@link ICarEvsStreamCallback} listener to unregister.
641      */
642     @Override
stopVideoStream(@onNull ICarEvsStreamCallback callback)643     public void stopVideoStream(@NonNull ICarEvsStreamCallback callback) {
644         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
645         Objects.requireNonNull(callback);
646 
647         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
648         if (types == null || types.isEmpty()) {
649             Slogf.i(TAG_EVS, "Ignores a request to stop a video stream for unknown callback %s.",
650                     callback);
651             return;
652         }
653 
654         for (int i = 0; i < types.size(); i++) {
655             int type = types.valueAt(i);
656             StateMachine instance = mServiceInstances.get(type);
657             if (instance == null) {
658                 Slogf.w(TAG_EVS, "CarEvsService is not configured for a service type %d.", type);
659                 continue;
660             }
661 
662             instance.requestStopVideoStream(callback);
663         }
664         mCallbackToServiceType.remove(callback.asBinder());
665     }
666 
667     /**
668      * Requests to stop a video stream from a given service type.
669      *
670      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
671      *
672      * @param type {@link CarEvsServiceType} to stop listening.
673      * @param callback {@link ICarEvsStreamCallback} listener to unregister.
674      */
675     @Override
stopVideoStreamFrom(@arEvsServiceType int type, @NonNull ICarEvsStreamCallback callback)676     public void stopVideoStreamFrom(@CarEvsServiceType int type,
677             @NonNull ICarEvsStreamCallback callback) {
678         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
679         Objects.requireNonNull(callback);
680 
681         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
682         int idx = types.indexOf(type);
683         if (idx < 0) {
684             Slogf.i(TAG_EVS, "Ignores a request to stop a video stream that is not active.");
685             return;
686         }
687 
688         StateMachine instance = mServiceInstances.get(type);
689         if (instance == null) {
690             Slogf.w(TAG_EVS, "CarEvsService is not configured for a service type %d.", type);
691             return;
692         }
693 
694         instance.requestStopVideoStream(callback);
695         types.remove(type);
696         if (types.isEmpty()) {
697             // No more active stream for this callback object.
698             mCallbackToServiceType.remove(callback.asBinder());
699         }
700     }
701 
702     /**
703      * Returns an used buffer to EVS service.
704      *
705      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
706      *
707      * @param buffer A consumed CarEvsBufferDescriptor object.  This would not be used and returned
708      *               to the native EVS service.
709      * @throws IllegalArgumentException if a passed buffer has an unregistered identifier.
710      */
711     @Override
returnFrameBuffer(@onNull CarEvsBufferDescriptor buffer)712     public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
713         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
714         Objects.requireNonNull(buffer);
715 
716         // 8 MSB tells the service type of this buffer.
717         mServiceInstances.get(buffer.getType()).doneWithFrame(buffer.getId());
718     }
719 
720     /**
721      * Returns a current status of CarEvsService's REARVIEW service type.
722      *
723      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
724      * access.
725      *
726      * @return {@link android.car.evs.CarEvsStatus}
727      */
728     @Override
729     @Nullable
getCurrentStatus(@arEvsServiceType int type)730     public CarEvsStatus getCurrentStatus(@CarEvsServiceType int type) {
731         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
732 
733         // This public API only returns current status of SERVICE_TYPE_REARVIEW. To get other
734         // services' status, please register a status listener via
735         // CarEvsService.registerStatusListener() API.
736         StateMachine instance = mServiceInstances.get(type);
737         if (instance == null) {
738             return null;
739         }
740 
741         return instance.getCurrentStatus();
742     }
743 
744     /**
745      * Returns a session token to be used to request the services.
746      *
747      * <p>Requires {@link android.car.Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY} permission to access.
748      *
749      * @return IBinder object as a session token.
750      * @throws IllegalStateException if we fail to find System UI package.
751      */
752     @Override
generateSessionToken()753     public IBinder generateSessionToken() {
754         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY);
755 
756         // TODO(b/191940626): With the unlimited multi-client supports, a validity of a session
757         //                    token does not make any different in handling streaming clients. This
758         //                    needs to be modified with a logic to manage the maximum number of
759         //                    streaming clients per service.
760         String systemUiPackageName = PackageManagerHelper.getSystemUiPackageName(mContext);
761         IBinder token = new Binder();
762         try {
763             int systemUiUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(),
764                     systemUiPackageName, UserHandle.SYSTEM.getIdentifier());
765             int callerUid = Binder.getCallingUid();
766             if (systemUiUid == callerUid) {
767                 mSessionTokens.add(token);
768             } else {
769                 throw new SecurityException("SystemUI only can generate SessionToken");
770             }
771         } catch (NameNotFoundException e) {
772             throw new IllegalStateException(systemUiPackageName + " package not found", e);
773         } finally {
774             return token;
775         }
776     }
777 
778     /**
779      * Returns whether or not a given service type is supported.
780      *
781      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
782      * access.
783      */
784     @Override
isSupported(@arEvsServiceType int type)785     public boolean isSupported(@CarEvsServiceType int type) {
786         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
787 
788         StateMachine instance = mServiceInstances.get(type);
789         if (instance == null) {
790             return false;
791         }
792 
793         return instance.isConnected();
794     }
795 
796     /**
797      * Sets a camera device for the rearview.
798      *
799      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
800      *
801      * @param id A string identifier of a target camera device.
802      * @return This method return a false if this runs in a release build; otherwise, this returns
803      *         true.
804      */
setRearviewCameraIdFromCommand(@onNull String id)805     public boolean setRearviewCameraIdFromCommand(@NonNull String id) {
806         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
807         Objects.requireNonNull(id);
808 
809         if (!mIsEvsAvailable) {
810             Slogf.e(TAG_EVS, "CarEvsService is not available.");
811             return false;
812         }
813 
814         if (!BuildHelper.isDebuggableBuild()) {
815             // This method is not allowed in the release build.
816             Slogf.e(TAG_EVS, "It is not allowed to change a camera assigned to the rearview " +
817                     "in the release build.");
818             return false;
819         }
820 
821         mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW).setCameraId(id);
822         return true;
823     }
824 
825     /**
826      * Sets a camera device for a given service type.
827      *
828      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
829      *
830      * @param type A service type to assign a camera associated with a given string identifier.
831      *             Please use '*' part of CarEvsManager.SERVICE_TYPE_* constants.
832      * @param id A string identifier of a target camera device.
833      * @return This method return true if it successfully programs a camera id for a given service
834      *         type. Otherwise, this will return false.
835      */
setCameraIdFromCommand(@onNull String type, @NonNull String id)836     public boolean setCameraIdFromCommand(@NonNull String type, @NonNull String id) {
837         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
838 
839         if (!BuildHelper.isDebuggableBuild()) {
840             // This method is not allowed in the release build.
841             Slogf.e(TAG_EVS, "It is not allowed to change a camera id assigned to the service " +
842                     "in the release build.");
843             return false;
844         }
845 
846         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
847         StateMachine instance = mServiceInstances.get(serviceType);
848         if (instance == null) {
849             Slogf.e(TAG_EVS, "Ignores a request to set a camera %s for unavailable service %s.",
850                     id, type);
851             return false;
852         }
853 
854         instance.setCameraId(id);
855         return true;
856     }
857 
858     /**
859      * Gets an identifier of a current camera device for the rearview.
860      *
861      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
862      * access.
863      *
864      * @return A string identifier of current rearview camera device.
865      */
866     @Nullable
getRearviewCameraIdFromCommand()867     public String getRearviewCameraIdFromCommand() {
868         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
869 
870         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
871         if (instance == null) {
872             Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable " +
873                     "REARVIEW service.");
874             return null;
875         }
876 
877         return instance.getCameraId();
878     }
879 
880     /**
881      * Gets a String identifier of a camera assigned to a given service type.
882      *
883      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
884      * access.
885      *
886      * @param type A service type to get a camera identifier. Please use "*" part of
887      *             CarEvsManager.SERVICE_TYPE_* constants.
888      * @return A string identifier of a camera assigned to a given service type.
889      */
890     @Nullable
getCameraIdFromCommand(@onNull String type)891     public String getCameraIdFromCommand(@NonNull String type) {
892         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
893         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
894         StateMachine instance = mServiceInstances.get(serviceType);
895         if (instance == null) {
896             Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable service %s.",
897                     type);
898             return null;
899         }
900 
901         return instance.getCameraId();
902     }
903 
904     /**
905      * Enables a given service type with a specified camera device.
906      *
907      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
908      *
909      * @param typeString A service type to get a camera identifier. Please use "*" part of
910      *             CarEvsManager.SERVICE_TYPE_* constants.
911      * @param cameraId A string identifier of a target camera device. A camera associated with this
912      *                 id must not be assigned to any service type.
913      * @return false if a requested service type is already enabled or a specific camera id is
914      *               already assigned to other service types.
915      *         true otherwise.
916      */
enableServiceTypeFromCommand(@onNull String typeString, @NonNull String cameraId)917     public boolean enableServiceTypeFromCommand(@NonNull String typeString,
918             @NonNull String cameraId) {
919         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
920 
921         if (!mIsEvsAvailable) {
922             Slogf.e(TAG_EVS, "Failed to enable %s service due to missing dependencies.",
923                     typeString);
924             return false;
925         }
926 
927         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(typeString);
928         for (int i = 0; i < mServiceInstances.size(); i++) {
929             int type = mServiceInstances.keyAt(i);
930             StateMachine instance = mServiceInstances.valueAt(i);
931 
932             if (type == serviceType || cameraId.equals(instance.getCameraId())) {
933                 Slogf.e(TAG_EVS, "A requested service type is already provided by " +
934                         " or a given camera id is used by %s.", instance);
935                 return false;
936             }
937         }
938 
939         StateMachine s = new StateMachine(mContext, mBuiltinContext, this, null,
940                 serviceType, cameraId);
941         if (!s.init()) {
942             Slogf.e(TAG_EVS, "Failed to initialize a requested service type.");
943             return false;
944         }
945         s.connectToHalServiceIfNecessary();
946         mServiceInstances.put(serviceType, s);
947         return true;
948     }
949 
950     /**
951      * Checks whether or not a given service type is enabled.
952      *
953      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
954      * access.
955      *
956      * @param type A service type to get a camera identifier. Please use "*" part of
957      *             CarEvsManager.SERVICE_TYPE_* constants.
958      * @return true if a given service type is available.
959      *         false otherwise.
960      */
isServiceTypeEnabledFromCommand(@onNull String type)961     public boolean isServiceTypeEnabledFromCommand(@NonNull String type) {
962         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
963         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
964         return mServiceInstances.get(serviceType) != null;
965     }
966 
967     /** Checks whether or not a given token is valid. */
isSessionToken(IBinder token)968     boolean isSessionToken(IBinder token) {
969         return mSessionTokens.contains(token);
970     }
971 
972     /** Invalidate a given token. */
invalidateSessionToken(IBinder token)973     void invalidateSessionToken(IBinder token) {
974         mSessionTokens.remove(token);
975     }
976 
977     /** Package-private version of generateSessionToken() method. */
978     @NonNull
generateSessionTokenInternal()979     IBinder generateSessionTokenInternal() {
980         IBinder token = new Binder();
981         mSessionTokens.add(token);
982         return token;
983     }
984 
985     /**
986      * Manually sets a stream callback.
987      */
988     @VisibleForTesting
addStreamCallback(@arEvsServiceType int type, @Nullable ICarEvsStreamCallback callback)989     void addStreamCallback(@CarEvsServiceType int type, @Nullable ICarEvsStreamCallback callback) {
990         StateMachine instance = mServiceInstances.get(type);
991         if (instance == null || callback == null) {
992             return;
993         }
994 
995         instance.addStreamCallback(callback);
996 
997         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
998         if (types == null) {
999             mCallbackToServiceType.put(callback.asBinder(),
1000                     new ArraySet<>(Set.of(type)));
1001         } else {
1002             types.add(type);
1003         }
1004     }
1005 
1006     /** Tells whether or not the latest EVS HAL event was requesting to start an activity. */
needToStartActivity()1007     boolean needToStartActivity() {
1008         synchronized (mLock) {
1009             return mLastEvsHalEvent != null && mLastEvsHalEvent.isRequestingToStartActivity();
1010         }
1011     }
1012 
1013     /**
1014      * Manually sets a current service state.
1015      */
1016     @VisibleForTesting
setServiceState(@arEvsServiceType int type, @CarEvsServiceState int newState)1017     void setServiceState(@CarEvsServiceType int type, @CarEvsServiceState int newState) {
1018         StateMachine instance = mServiceInstances.get(type);
1019         if (instance == null) {
1020             return;
1021         }
1022 
1023         instance.setState(newState);
1024     }
1025 
1026     /**
1027      * Manually chooses to use a gear selection property or not.
1028      */
1029     @VisibleForTesting
setToUseGearSelection(boolean useGearSelection)1030     void setToUseGearSelection(boolean useGearSelection) {
1031         mUseGearSelection = useGearSelection;
1032     }
1033 
1034     /**
1035      * Manually sets the last EVS HAL event.
1036      */
1037     @VisibleForTesting
setLastEvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on)1038     void setLastEvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) {
1039         synchronized (mLock) {
1040             mLastEvsHalEvent = new EvsHalEvent(timestamp, type, on);
1041         }
1042     }
1043 
1044     /** Notifies the service status gets changed */
broadcastStateTransition(int type, int state)1045     void broadcastStateTransition(int type, int state) {
1046         int idx = mStatusListeners.beginBroadcast();
1047         while (idx-- > 0) {
1048             ICarEvsStatusListener listener = mStatusListeners.getBroadcastItem(idx);
1049             try {
1050                 listener.onStatusChanged(new CarEvsStatus(type, state));
1051             } catch (RemoteException e) {
1052                 // Likely the binder death incident.
1053                 Slogf.e(TAG_EVS, Log.getStackTraceString(e));
1054             }
1055         }
1056         mStatusListeners.finishBroadcast();
1057     }
1058 
handlePropertyEvent(CarPropertyEvent event)1059     private void handlePropertyEvent(CarPropertyEvent event) {
1060         if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
1061             // CarEvsService is interested only in the property change event.
1062             return;
1063         }
1064 
1065         CarPropertyValue value = event.getCarPropertyValue();
1066         if (value.getPropertyId() != VehiclePropertyIds.GEAR_SELECTION) {
1067             // CarEvsService is interested only in the GEAR_SELECTION property.
1068             return;
1069         }
1070 
1071         long timestamp = value.getTimestamp();
1072         boolean isReverseGear;
1073         synchronized (mLock) {
1074             if (timestamp != 0 && timestamp <= mLastEvsHalEvent.getTimestamp()) {
1075                 if (DBG) {
1076                     Slogf.d(TAG_EVS,
1077                             "Ignoring GEAR_SELECTION change happened past, timestamp = " +
1078                             timestamp + ", last event was at " + mLastEvsHalEvent.getTimestamp());
1079                 }
1080                 return;
1081             }
1082 
1083 
1084             isReverseGear = (Integer) value.getValue() == VehicleGear.GEAR_REVERSE;
1085             mLastEvsHalEvent = new EvsHalEvent(timestamp, CarEvsManager.SERVICE_TYPE_REARVIEW,
1086                     isReverseGear);
1087         }
1088 
1089         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
1090         if (instance == null) {
1091             Slogf.i(TAG_EVS,
1092                     "Ignore a GEAR_SELECTION event because the rearview service is not available.");
1093             return;
1094         }
1095 
1096         if (isReverseGear) {
1097             // Request to start the rearview activity when the gear is shifted into the reverse
1098             // position.
1099             if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
1100                 Slogf.w(TAG_EVS, "Failed to request the rearview activity.");
1101             }
1102         } else {
1103             // Request to stop the rearview activity when the gear is shifted from the reverse
1104             // position to other positions.
1105             if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
1106                 Slogf.i(TAG_EVS, "Failed to stop the rearview activity.");
1107             }
1108         }
1109     }
1110 
1111     /** Handles a disconnection of a status monitoring client. */
handleClientDisconnected(ICarEvsStatusListener listener)1112     private void handleClientDisconnected(ICarEvsStatusListener listener) {
1113         mStatusListeners.unregister(listener);
1114         if (mStatusListeners.getRegisteredCallbackCount() == 0) {
1115             Slogf.d(TAG_EVS, "Last status listener has been disconnected.");
1116         }
1117     }
1118 }
1119