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