• 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 android.car.evs;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SuppressLint;
25 import android.annotation.SystemApi;
26 import android.car.Car;
27 import android.car.CarManagerBase;
28 import android.car.annotation.AddedInOrBefore;
29 import android.car.annotation.RequiredFeature;
30 import android.car.builtin.util.Slogf;
31 import android.os.Binder;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import com.android.internal.annotations.GuardedBy;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.lang.ref.WeakReference;
41 import java.util.Objects;
42 import java.util.concurrent.Executor;
43 
44 /**
45  * Provides an application interface for interativing with the Extended View System service.
46  *
47  * @hide
48  */
49 @RequiredFeature(Car.CAR_EVS_SERVICE)
50 @SystemApi
51 public final class CarEvsManager extends CarManagerBase {
52     @AddedInOrBefore(majorVersion = 33)
53     public static final String EXTRA_SESSION_TOKEN = "android.car.evs.extra.SESSION_TOKEN";
54 
55     private static final String TAG = CarEvsManager.class.getSimpleName();
56     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
57 
58     private final ICarEvsService mService;
59     private final Object mStreamLock = new Object();
60 
61     @GuardedBy("mStreamLock")
62     private CarEvsStreamCallback mStreamCallback;
63 
64     @GuardedBy("mStreamLock")
65     private Executor mStreamCallbackExecutor;
66 
67     private final CarEvsStreamListenerToService mStreamListenerToService =
68             new CarEvsStreamListenerToService(this);
69 
70     private final Object mStatusLock = new Object();
71 
72     @GuardedBy("mStatusLock")
73     private CarEvsStatusListener mStatusListener;
74 
75     @GuardedBy("mStatusLock")
76     private Executor mStatusListenerExecutor;
77 
78     private final CarEvsStatusListenerToService mStatusListenerToService =
79             new CarEvsStatusListenerToService(this);
80 
81     /**
82      * Service type to represent the rearview camera service.
83      */
84     @AddedInOrBefore(majorVersion = 33)
85     public static final int SERVICE_TYPE_REARVIEW = 0;
86 
87     /**
88      * Service type to represent the surround view service.
89      */
90     @AddedInOrBefore(majorVersion = 33)
91     public static final int SERVICE_TYPE_SURROUNDVIEW = 1;
92 
93     /** @hide */
94     @IntDef (prefix = {"SERVICE_TYPE_"}, value = {
95             SERVICE_TYPE_REARVIEW,
96             SERVICE_TYPE_SURROUNDVIEW,
97     })
98     @Retention(RetentionPolicy.SOURCE)
99     public @interface CarEvsServiceType {}
100 
101     /**
102      * State that a corresponding service type is not available.
103      */
104     @AddedInOrBefore(majorVersion = 33)
105     public static final int SERVICE_STATE_UNAVAILABLE = 0;
106 
107     /**
108      * State that a corresponding service type is inactive; it's available but not used
109      * by any clients.
110      */
111     @AddedInOrBefore(majorVersion = 33)
112     public static final int SERVICE_STATE_INACTIVE = 1;
113 
114     /**
115      * State that CarEvsManager received a service request from the client.
116      */
117     @AddedInOrBefore(majorVersion = 33)
118     public static final int SERVICE_STATE_REQUESTED = 2;
119 
120     /**
121      * State that a corresponding service type is actively being used.
122      */
123     @AddedInOrBefore(majorVersion = 33)
124     public static final int SERVICE_STATE_ACTIVE = 3;
125 
126     /** @hide */
127     @IntDef (prefix = {"SERVICE_STATE_"}, value = {
128             SERVICE_STATE_UNAVAILABLE,
129             SERVICE_STATE_INACTIVE,
130             SERVICE_STATE_REQUESTED,
131             SERVICE_STATE_ACTIVE
132     })
133     @Retention(RetentionPolicy.SOURCE)
134     public @interface CarEvsServiceState {}
135 
136     /**
137      * This is a default EVS stream event type.
138      */
139     @AddedInOrBefore(majorVersion = 33)
140     public static final int STREAM_EVENT_NONE = 0;
141 
142     /**
143      * EVS stream event to notify a video stream has been started.
144      */
145     @AddedInOrBefore(majorVersion = 33)
146     public static final int STREAM_EVENT_STREAM_STARTED = 1;
147 
148     /**
149      * EVS stream event to notify a video stream has been stopped.
150      */
151     @AddedInOrBefore(majorVersion = 33)
152     public static final int STREAM_EVENT_STREAM_STOPPED = 2;
153 
154     /**
155      * EVS stream event to notify that a video stream is dropped.
156      */
157     @AddedInOrBefore(majorVersion = 33)
158     public static final int STREAM_EVENT_FRAME_DROPPED = 3;
159 
160     /**
161      * EVS stream event occurs when a timer for a new frame's arrival is expired.
162      */
163     @AddedInOrBefore(majorVersion = 33)
164     public static final int STREAM_EVENT_TIMEOUT = 4;
165 
166     /**
167      * EVS stream event occurs when a camera parameter is changed.
168      */
169     @AddedInOrBefore(majorVersion = 33)
170     public static final int STREAM_EVENT_PARAMETER_CHANGED = 5;
171 
172     /**
173      * EVS stream event to notify the primary owner has been changed.
174      */
175     @AddedInOrBefore(majorVersion = 33)
176     public static final int STREAM_EVENT_PRIMARY_OWNER_CHANGED = 6;
177 
178     /**
179      * Other EVS stream errors
180      */
181     @AddedInOrBefore(majorVersion = 33)
182     public static final int STREAM_EVENT_OTHER_ERRORS = 7;
183 
184     /** @hide */
185     @IntDef(prefix = {"STREAM_EVENT_"}, value = {
186         STREAM_EVENT_NONE,
187         STREAM_EVENT_STREAM_STARTED,
188         STREAM_EVENT_STREAM_STOPPED,
189         STREAM_EVENT_FRAME_DROPPED,
190         STREAM_EVENT_TIMEOUT,
191         STREAM_EVENT_PARAMETER_CHANGED,
192         STREAM_EVENT_PRIMARY_OWNER_CHANGED,
193         STREAM_EVENT_OTHER_ERRORS
194     })
195     @Retention(RetentionPolicy.SOURCE)
196     public @interface CarEvsStreamEvent {}
197 
198     /**
199      * Status to tell that a request is successfully processed.
200      */
201     @AddedInOrBefore(majorVersion = 33)
202     public static final int ERROR_NONE = 0;
203 
204     /**
205      * Status to tell a requested service is not available.
206      */
207     @AddedInOrBefore(majorVersion = 33)
208     public static final int ERROR_UNAVAILABLE = -1;
209 
210     /**
211      * Status to tell CarEvsService is busy to serve the privileged client.
212      */
213     @AddedInOrBefore(majorVersion = 33)
214     public static final int ERROR_BUSY = -2;
215 
216     /** @hide */
217     @IntDef(prefix = {"ERROR_"}, value = {
218         ERROR_NONE,
219         ERROR_UNAVAILABLE,
220         ERROR_BUSY
221     })
222     @Retention(RetentionPolicy.SOURCE)
223     public @interface CarEvsError {}
224 
225     /**
226      * Gets an instance of CarEvsManager
227      *
228      * CarEvsManager manages {@link com.android.car.evs.CarEvsService} and provides APIs that the
229      * clients can use the Extended View System service.
230      *
231      * This must not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
232      *
233      * @hide
234      */
CarEvsManager(Car car, IBinder service)235     public CarEvsManager(Car car, IBinder service) {
236         super(car);
237 
238         // Gets CarEvsService
239         mService = ICarEvsService.Stub.asInterface(service);
240     }
241 
242     /** @hide */
243     @Override
244     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()245     public void onCarDisconnected() {
246         synchronized (mStatusLock) {
247             mStatusListener = null;
248             mStatusListenerExecutor = null;
249         }
250 
251         synchronized (mStreamLock) {
252             mStreamCallback = null;
253             mStreamCallbackExecutor = null;
254         }
255     }
256 
257     /**
258      * Application registers {@link #CarEvsStatusListener} object to receive requests to control
259      * the activity and monitor the status of the EVS service.
260      */
261     public interface CarEvsStatusListener {
262         /**
263          * Called when the status of EVS service is changed.
264          *
265          * @param type A type of EVS service; e.g. the rearview.
266          * @param state Updated service state; e.g. the service is started.
267          */
268         @AddedInOrBefore(majorVersion = 33)
onStatusChanged(@onNull CarEvsStatus status)269         void onStatusChanged(@NonNull CarEvsStatus status);
270     }
271 
272     /**
273      * Class implementing the listener interface {@link com.android.car.ICarEvsStatusListener}
274      * to listen status updates across the binder interface.
275      */
276     private static class CarEvsStatusListenerToService extends ICarEvsStatusListener.Stub {
277         private final WeakReference<CarEvsManager> mManager;
278 
CarEvsStatusListenerToService(CarEvsManager manager)279         CarEvsStatusListenerToService(CarEvsManager manager) {
280             mManager = new WeakReference<>(manager);
281         }
282 
283         @Override
onStatusChanged(@onNull CarEvsStatus status)284         public void onStatusChanged(@NonNull CarEvsStatus status) {
285             Objects.requireNonNull(status);
286 
287             CarEvsManager mgr = mManager.get();
288             if (mgr != null) {
289                 mgr.handleServiceStatusChanged(status);
290             }
291         }
292     }
293 
294     /**
295      * Gets the {@link #CarEvsStatus} from the service listener {@link
296      * #CarEvsStatusListenerToService} and forwards it to the client.
297      *
298      * @param status {@link android.car.evs.CarEvsStatus}
299      */
handleServiceStatusChanged(CarEvsStatus status)300     private void handleServiceStatusChanged(CarEvsStatus status) {
301         if (DBG) {
302             Slogf.d(TAG, "Service state changed: service = " + status.getServiceType()
303                     + ", state = " + status.getState());
304         }
305 
306         final CarEvsStatusListener listener;
307         final Executor executor;
308         synchronized (mStatusLock) {
309             listener = mStatusListener;
310             executor = mStatusListenerExecutor;
311         }
312 
313         if (listener != null) {
314             executor.execute(() -> listener.onStatusChanged(status));
315         } else if (DBG) {
316             Slogf.w(TAG, "No client seems active; a received event is ignored.");
317         }
318     }
319 
320     /**
321      * Sets {@link #CarEvsStatusListener} object to receive requests to control the activity
322      * view and EVS data.
323      *
324      * @param executor {@link java.util.concurrent.Executor} to execute callbacks.
325      * @param listener {@link #CarEvsStatusListener} to register.
326      * @throws IllegalStateException if this method is called while a registered status listener
327      *         exists.
328      */
329     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
330     @AddedInOrBefore(majorVersion = 33)
setStatusListener(@onNull @allbackExecutor Executor executor, @NonNull CarEvsStatusListener listener)331     public void setStatusListener(@NonNull @CallbackExecutor Executor executor,
332             @NonNull CarEvsStatusListener listener) {
333         if (DBG) {
334             Slogf.d(TAG, "Registering a service monitoring listener.");
335         }
336 
337         Objects.requireNonNull(listener);
338         Objects.requireNonNull(executor);
339 
340         if (mStatusListener != null) {
341             throw new IllegalStateException("A status listener is already registered.");
342         }
343 
344         synchronized (mStatusLock) {
345             mStatusListener = listener;
346             mStatusListenerExecutor = executor;
347         }
348 
349         try {
350             mService.registerStatusListener(mStatusListenerToService);
351         } catch (RemoteException err) {
352             handleRemoteExceptionFromCarService(err);
353         }
354     }
355 
356     /**
357      * Stops getting callbacks to control the camera viewing activity by clearing
358      * {@link #CarEvsStatusListener} object.
359      */
360     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
361     @AddedInOrBefore(majorVersion = 33)
clearStatusListener()362     public void clearStatusListener() {
363         if (DBG) {
364             Slogf.d(TAG, "Unregistering a service monitoring callback.");
365         }
366 
367         synchronized (mStatusLock) {
368             mStatusListener = null;
369         }
370 
371         try{
372             mService.unregisterStatusListener(mStatusListenerToService);
373         } catch (RemoteException err) {
374             handleRemoteExceptionFromCarService(err);
375         }
376     }
377 
378     /**
379      * Application registers {@link #CarEvsStreamCallback} object to listen to EVS services' status
380      * changes.
381      *
382      * CarEvsManager supports two client types; one is a System UI type client and another is a
383      * normal Android activity type client.  The former client type has a priority over
384      * the latter type client and CarEvsManager allows only a single client of each type to
385      * subscribe.
386      */
387     // TODO(b/174572385): Removes below lint suppression
388     @SuppressLint("CallbackInterface")
389     public interface CarEvsStreamCallback {
390         /**
391          * Called when any EVS stream events occur.
392          *
393          * @param event {@link #CarEvsStreamEvent}; e.g. a stream started
394          */
395         @AddedInOrBefore(majorVersion = 33)
onStreamEvent(@arEvsStreamEvent int event)396         default void onStreamEvent(@CarEvsStreamEvent int event) {}
397 
398         /**
399          * Called when new frame arrives.
400          *
401          * @param buffer {@link android.car.evs.CarEvsBufferDescriptor} contains a EVS frame
402          */
403         @AddedInOrBefore(majorVersion = 33)
onNewFrame(@onNull CarEvsBufferDescriptor buffer)404         default void onNewFrame(@NonNull CarEvsBufferDescriptor buffer) {}
405     }
406 
407     /**
408      * Class implementing the listener interface and gets callbacks from the
409      * {@link com.android.car.ICarEvsStreamCallback} across the binder interface.
410      */
411     private static class CarEvsStreamListenerToService extends ICarEvsStreamCallback.Stub {
412         private final WeakReference<CarEvsManager> mManager;
413 
CarEvsStreamListenerToService(CarEvsManager manager)414         CarEvsStreamListenerToService(CarEvsManager manager) {
415             mManager = new WeakReference<>(manager);
416         }
417 
418         @Override
onStreamEvent(@arEvsStreamEvent int event)419         public void onStreamEvent(@CarEvsStreamEvent int event) {
420             CarEvsManager manager = mManager.get();
421             if (manager != null) {
422                 manager.handleStreamEvent(event);
423             }
424         }
425 
426         @Override
onNewFrame(CarEvsBufferDescriptor buffer)427         public void onNewFrame(CarEvsBufferDescriptor buffer) {
428             CarEvsManager manager = mManager.get();
429             if (manager != null) {
430                 manager.handleNewFrame(buffer);
431             }
432         }
433     }
434 
435     /**
436      * Gets the {@link #CarEvsStreamEvent} from the service listener
437      * {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
438      * to the manager.
439      *
440      * @param event {@link #CarEvsStreamEvent} from the service this manager subscribes to.
441      */
handleStreamEvent(@arEvsStreamEvent int event)442     private void handleStreamEvent(@CarEvsStreamEvent int event) {
443         if (DBG) {
444             Slogf.d(TAG, "Received: " + event);
445         }
446 
447         final CarEvsStreamCallback callback;
448         final Executor executor;
449         synchronized (mStreamLock) {
450             callback = mStreamCallback;
451             executor = mStreamCallbackExecutor;
452         }
453 
454         if (callback != null) {
455             executor.execute(() -> callback.onStreamEvent(event));
456         } else if (DBG) {
457             Slogf.w(TAG, "No client seems active; a current stream event is ignored.");
458         }
459     }
460 
461     /**
462      * Gets the {@link android.car.evs.CarEvsBufferDescriptor} from the service listener
463      * {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
464      * to the manager.
465      *
466      * @param buffer {@link android.car.evs.CarEvsBufferDescriptor}
467      */
handleNewFrame(@onNull CarEvsBufferDescriptor buffer)468     private void handleNewFrame(@NonNull CarEvsBufferDescriptor buffer) {
469         Objects.requireNonNull(buffer);
470         if (DBG) {
471             Slogf.d(TAG, "Received a buffer: " + buffer);
472         }
473 
474         final CarEvsStreamCallback callback;
475         final Executor executor;
476         synchronized (mStreamLock) {
477             callback = mStreamCallback;
478             executor = mStreamCallbackExecutor;
479         }
480 
481         if (callback != null) {
482             executor.execute(() -> callback.onNewFrame(buffer));
483         } else {
484             if (DBG) {
485                 Slogf.w(TAG, "A buffer is being returned back to the service because no active "
486                         + "clients exist.");
487             }
488             returnFrameBuffer(buffer);
489         }
490     }
491 
492     /**
493      * Returns a consumed {@link android.car.evs.CarEvsBufferDescriptor}.
494      *
495      * @param buffer {@link android.car.evs.CarEvsBufferDescriptor} to be returned to
496      * the EVS service.
497      */
498     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
499     @AddedInOrBefore(majorVersion = 33)
returnFrameBuffer(@onNull CarEvsBufferDescriptor buffer)500     public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
501         Objects.requireNonNull(buffer);
502         try {
503             mService.returnFrameBuffer(buffer.getId());
504         } catch (RemoteException err) {
505             handleRemoteExceptionFromCarService(err);
506         } finally {
507             // We are done with this HardwareBuffer object.
508             buffer.getHardwareBuffer().close();
509         }
510     }
511 
512     /**
513      * Requests the system to start an activity for {@link #CarEvsServiceType}.
514      *
515      * @param type A type of EVS service to start.
516      * @return {@link #CarEvsError} to tell the result of the request.
517      *         {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
518      *         the native EVS service or the binder transaction fails.
519      *         {@link #ERROR_BUSY} will be returned if the CarEvsService is in the
520      *         {@link #SERVICE_STATE_REQUESTED} for a different service type.
521      *         If the same service type is running, this will return {@link #ERROR_NONE}.
522      *         {@link #ERROR_NONE} will be returned for all other cases.
523      */
524     @RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
525     @AddedInOrBefore(majorVersion = 33)
startActivity(@arEvsServiceType int type)526     public @CarEvsError int startActivity(@CarEvsServiceType int type) {
527         try {
528             return mService.startActivity(type);
529         } catch (RemoteException err) {
530             handleRemoteExceptionFromCarService(err);
531         }
532 
533         return ERROR_UNAVAILABLE;
534     }
535 
536     /**
537      * Requests the system to stop a current activity launched via {@link #startActivity}.
538      */
539     @RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
540     @AddedInOrBefore(majorVersion = 33)
stopActivity()541     public void stopActivity() {
542         try {
543             mService.stopActivity();
544         } catch (RemoteException err) {
545             handleRemoteExceptionFromCarService(err);
546         }
547     }
548 
549     /**
550      * Requests to start a video stream from {@link #CarEvsServiceType}.
551      *
552      * @param type A type of EVS service.
553      * @param token A session token that is issued to privileged clients.  SystemUI must obtain this
554      *        token obtain this via {@link #generateSessionToken} and pass it to the activity, to
555      *        prioritize its service requests.
556      *        TODO(b/179517136): Defines an Intent extra
557      * @param callback {@link #CarEvsStreamCallback} to listen to the stream.
558      * @param executor {@link java.util.concurrent.Executor} to run a callback.
559      * @return {@link #CarEvsError} to tell the result of the request.
560      *         {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
561      *         the native EVS service or the binder transaction fails.
562      *         {@link #ERROR_BUSY} will be returned if the CarEvsService is handling a service
563      *         request with a valid session token.
564      *         {@link #ERROR_NONE} for all other cases.
565      */
566     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
567     @AddedInOrBefore(majorVersion = 33)
startVideoStream( @arEvsServiceType int type, @Nullable IBinder token, @NonNull @CallbackExecutor Executor executor, @NonNull CarEvsStreamCallback callback)568     public @CarEvsError int startVideoStream(
569             @CarEvsServiceType int type,
570             @Nullable IBinder token,
571             @NonNull @CallbackExecutor Executor executor,
572             @NonNull CarEvsStreamCallback callback) {
573         if (DBG) {
574             Slogf.d(TAG, "Received a request to start a video stream: " + type);
575         }
576 
577         Objects.requireNonNull(executor);
578         Objects.requireNonNull(callback);
579 
580         synchronized (mStreamLock) {
581             mStreamCallback = callback;
582             mStreamCallbackExecutor = executor;
583         }
584 
585         int status = ERROR_UNAVAILABLE;
586         try {
587             // Requests the service to start a video stream
588             status = mService.startVideoStream(type, token, mStreamListenerToService);
589         } catch (RemoteException err) {
590             handleRemoteExceptionFromCarService(err);
591         } finally {
592             return status;
593         }
594     }
595 
596     /**
597      * Requests to stop a current {@link #CarEvsServiceType}.
598      */
599     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
600     @AddedInOrBefore(majorVersion = 33)
stopVideoStream()601     public void stopVideoStream() {
602         synchronized (mStreamLock) {
603             if (mStreamCallback == null) {
604                 Slogf.e(TAG, "The service has not started yet.");
605                 return;
606             }
607 
608             // We're not interested in frames and events anymore.  The client can safely assume
609             // the service is stopped properly.
610             mStreamCallback = null;
611             mStreamCallbackExecutor = null;
612         }
613 
614         try {
615             mService.stopVideoStream(mStreamListenerToService);
616         } catch (RemoteException err) {
617             handleRemoteExceptionFromCarService(err);
618         }
619     }
620 
621     /**
622      * Queries the current status of CarEvsService
623      *
624      * @return {@link android.car.evs.CarEvsStatus} that describes current status of
625      * CarEvsService.
626      */
627     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
628     @NonNull
629     @AddedInOrBefore(majorVersion = 33)
getCurrentStatus()630     public CarEvsStatus getCurrentStatus() {
631         try {
632             return mService.getCurrentStatus();
633         } catch (RemoteException err) {
634             Slogf.e(TAG, "Failed to read a status of the service.");
635             return new CarEvsStatus(SERVICE_TYPE_REARVIEW, SERVICE_STATE_UNAVAILABLE);
636         }
637     }
638 
639     /**
640      * Generates a service session token.
641      *
642      * @return {@link IBinder} object as a service session token.
643      */
644     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY)
645     @NonNull
646     @AddedInOrBefore(majorVersion = 33)
generateSessionToken()647     public IBinder generateSessionToken() {
648         IBinder token = null;
649         try {
650             token =  mService.generateSessionToken();
651             if (token == null) {
652                 token = new Binder();
653             }
654         } catch (RemoteException err) {
655             Slogf.e(TAG, "Failed to generate a session token.");
656             token = new Binder();
657         } finally {
658             return token;
659         }
660 
661     }
662 
663     /**
664      * Returns whether or not a given service type is supported.
665      *
666      * @param type {@link CarEvsServiceType} to query
667      * @return true if a given service type is available on the system.
668      */
669     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
670     @AddedInOrBefore(majorVersion = 33)
isSupported(@arEvsServiceType int type)671     public boolean isSupported(@CarEvsServiceType int type) {
672         try {
673             return mService.isSupported(type);
674         } catch (RemoteException err) {
675             Slogf.e(TAG, "Failed to query a service availability");
676             return false;
677         }
678     }
679 }
680