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