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