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