1 /* 2 * Copyright (C) 2023 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.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 30 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEBUGGING_CODE; 31 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.car.builtin.util.Slogf; 35 import android.car.evs.CarEvsBufferDescriptor; 36 import android.car.evs.CarEvsManager; 37 import android.car.evs.CarEvsManager.CarEvsError; 38 import android.car.evs.CarEvsManager.CarEvsServiceState; 39 import android.car.evs.CarEvsManager.CarEvsServiceType; 40 import android.car.evs.CarEvsManager.CarEvsStreamEvent; 41 import android.car.evs.CarEvsStatus; 42 import android.car.evs.ICarEvsStreamCallback; 43 import android.content.ComponentName; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.hardware.HardwareBuffer; 47 import android.os.Binder; 48 import android.os.Bundle; 49 import android.os.Handler; 50 import android.os.HandlerThread; 51 import android.os.IBinder; 52 import android.os.Looper; 53 import android.os.RemoteCallbackList; 54 import android.os.RemoteException; 55 import android.util.ArraySet; 56 import android.util.SparseIntArray; 57 import android.util.Log; 58 59 import com.android.car.BuiltinPackageDependency; 60 import com.android.car.CarServiceUtils; 61 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 62 import com.android.car.internal.evs.CarEvsUtils; 63 import com.android.car.internal.evs.EvsHalWrapper; 64 import com.android.car.internal.util.IndentingPrintWriter; 65 import com.android.internal.annotations.GuardedBy; 66 import com.android.internal.annotations.VisibleForTesting; 67 68 import java.lang.ref.WeakReference; 69 import java.lang.reflect.Constructor; 70 import java.util.Objects; 71 72 /** CarEvsService state machine implementation to handle all state transitions. */ 73 final class StateMachine { 74 // Service request priorities 75 static final int REQUEST_PRIORITY_LOW = 0; 76 static final int REQUEST_PRIORITY_NORMAL = 1; 77 static final int REQUEST_PRIORITY_HIGH = 2; 78 79 // Timeout for a request to start a video stream with a valid token. 80 private static final int STREAM_START_REQUEST_TIMEOUT_MS = 3000; 81 82 private static final boolean DBG = Slogf.isLoggable(TAG_EVS, Log.DEBUG); 83 84 // Interval for connecting to the EVS HAL service trial. 85 private static final long EVS_HAL_SERVICE_BIND_RETRY_INTERVAL_MS = 1000; 86 // Object to recognize Runnable objects. 87 private static final String CALLBACK_RUNNABLE_TOKEN = StateMachine.class.getSimpleName(); 88 private static final String DEFAULT_CAMERA_ALIAS = "default"; 89 90 private final SparseIntArray mBufferRecords = new SparseIntArray(); 91 private final CarEvsService mService; 92 private final ComponentName mActivityName; 93 private final Context mContext; 94 private final EvsHalWrapper mHalWrapper; 95 private final HalCallback mHalCallback; 96 private final Handler mHandler; 97 private final HandlerThread mHandlerThread = 98 CarServiceUtils.getHandlerThread(getClass().getSimpleName()); 99 private final Object mLock = new Object(); 100 private final Runnable mActivityRequestTimeoutRunnable = () -> handleActivityRequestTimeout(); 101 private final String mLogTag; 102 private final @CarEvsServiceType int mServiceType; 103 104 private final class StreamCallbackList extends RemoteCallbackList<ICarEvsStreamCallback> { 105 @Override onCallbackDied(ICarEvsStreamCallback callback)106 public void onCallbackDied(ICarEvsStreamCallback callback) { 107 if (callback == null) { 108 return; 109 } 110 111 Slogf.w(mLogTag, "StreamCallback %s has died.", callback.asBinder()); 112 synchronized (mLock) { 113 if (StateMachine.this.needToStartActivityLocked()) { 114 if (StateMachine.this.startActivity(/* resetState= */ true) != ERROR_NONE) { 115 Slogf.e(mLogTag, "Failed to request the acticity."); 116 } 117 } else { 118 // Ensure we stops streaming. 119 StateMachine.this.handleClientDisconnected(callback); 120 } 121 } 122 } 123 } 124 125 @GuardedBy("mLock") 126 private String mCameraId; 127 128 // Current state. 129 @GuardedBy("mLock") 130 private int mState = SERVICE_STATE_UNAVAILABLE; 131 132 // Priority of a last service request. 133 @GuardedBy("mLock") 134 private int mLastRequestPriority = REQUEST_PRIORITY_LOW; 135 136 // The latest session token issued to the privileged client. 137 @GuardedBy("mLock") 138 private IBinder mSessionToken = null; 139 140 // A callback associated with current session token. 141 @GuardedBy("mLock") 142 private ICarEvsStreamCallback mPrivilegedCallback; 143 144 // This is a device name to override initial camera id. 145 private String mCameraIdOverride = null; 146 147 @VisibleForTesting 148 final class HalCallback implements EvsHalWrapper.HalEventCallback { 149 150 private final StreamCallbackList mCallbacks = new StreamCallbackList(); 151 152 /** EVS stream event handler called after a native handler. */ 153 @Override onHalEvent(int event)154 public void onHalEvent(int event) { 155 mHandler.postDelayed(() -> processStreamEvent(event), 156 CALLBACK_RUNNABLE_TOKEN, /* delayMillis= */ 0); 157 } 158 159 /** EVS frame handler called after a native handler. */ 160 @Override onFrameEvent(int id, HardwareBuffer buffer)161 public void onFrameEvent(int id, HardwareBuffer buffer) { 162 mHandler.postDelayed(() -> processNewFrame(id, buffer), 163 CALLBACK_RUNNABLE_TOKEN, /* delayMillis= */ 0); 164 } 165 166 /** EVS service death handler called after a native handler. */ 167 @Override onHalDeath()168 public void onHalDeath() { 169 // We have lost the Extended View System service. 170 execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_UNAVAILABLE); 171 connectToHalServiceIfNecessary(EVS_HAL_SERVICE_BIND_RETRY_INTERVAL_MS); 172 } 173 register(ICarEvsStreamCallback callback, IBinder token)174 boolean register(ICarEvsStreamCallback callback, IBinder token) { 175 return mCallbacks.register(callback, token); 176 } 177 unregister(ICarEvsStreamCallback callback)178 boolean unregister(ICarEvsStreamCallback callback) { 179 return mCallbacks.unregister(callback); 180 } 181 contains(ICarEvsStreamCallback target)182 boolean contains(ICarEvsStreamCallback target) { 183 boolean found = false; 184 synchronized (mCallbacks) { 185 int idx = mCallbacks.beginBroadcast(); 186 while (!found && idx-- > 0) { 187 ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx); 188 found = target.asBinder() == callback.asBinder(); 189 } 190 mCallbacks.finishBroadcast(); 191 } 192 return found; 193 } 194 isEmpty()195 boolean isEmpty() { 196 return mCallbacks.getRegisteredCallbackCount() == 0; 197 } 198 get()199 RemoteCallbackList get() { 200 return mCallbacks; 201 } 202 size()203 int size() { 204 return mCallbacks.getRegisteredCallbackCount(); 205 } 206 stop()207 void stop() { 208 synchronized (mCallbacks) { 209 int idx = mCallbacks.beginBroadcast(); 210 while (idx-- > 0) { 211 ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx); 212 requestStopVideoStream(callback); 213 } 214 mCallbacks.finishBroadcast(); 215 } 216 } 217 218 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)219 void dump(IndentingPrintWriter writer) { 220 writer.printf("Active clients:\n"); 221 writer.increaseIndent(); 222 synchronized (mCallbacks) { 223 int idx = mCallbacks.beginBroadcast(); 224 while (idx-- > 0) { 225 writer.printf("%s\n", mCallbacks.getBroadcastItem(idx).asBinder()); 226 } 227 mCallbacks.finishBroadcast(); 228 } 229 writer.decreaseIndent(); 230 } 231 232 /** Processes a streaming event and propagates it to registered clients */ processStreamEvent(@arEvsStreamEvent int event)233 private void processStreamEvent(@CarEvsStreamEvent int event) { 234 synchronized (mCallbacks) { 235 int idx = mCallbacks.beginBroadcast(); 236 while (idx-- > 0) { 237 ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx); 238 try { 239 int taggedEvent = CarEvsUtils.putTag(mServiceType, event); 240 callback.onStreamEvent(taggedEvent); 241 } catch (RemoteException e) { 242 Slogf.w(mLogTag, "Failed to forward an event to %s", callback); 243 } 244 } 245 mCallbacks.finishBroadcast(); 246 } 247 } 248 249 /** 250 * Processes a streaming event and propagates it to registered clients. 251 * 252 * @return Number of successful callbacks. 253 */ processNewFrame(int id, @NonNull HardwareBuffer buffer)254 private int processNewFrame(int id, @NonNull HardwareBuffer buffer) { 255 Objects.requireNonNull(buffer); 256 257 // Counts how many callbacks are successfully done. 258 int refcount = 0; 259 synchronized (mCallbacks) { 260 int idx = mCallbacks.beginBroadcast(); 261 while (idx-- > 0) { 262 ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx); 263 try { 264 int bufferId = CarEvsUtils.putTag(mServiceType, id); 265 callback.onNewFrame(new CarEvsBufferDescriptor(bufferId, buffer)); 266 refcount += 1; 267 } catch (RemoteException e) { 268 Slogf.w(mLogTag, "Failed to forward a frame to %s", callback); 269 } 270 } 271 mCallbacks.finishBroadcast(); 272 } 273 buffer.close(); 274 275 if (refcount > 0) { 276 synchronized (mLock) { 277 mBufferRecords.put(id, refcount); 278 } 279 } else { 280 Slogf.i(mLogTag, "No client is actively listening."); 281 mHalWrapper.doneWithFrame(id); 282 } 283 return refcount; 284 } 285 } 286 287 @VisibleForTesting createHalWrapper(Context builtinContext, EvsHalWrapper.HalEventCallback callback)288 static EvsHalWrapper createHalWrapper(Context builtinContext, 289 EvsHalWrapper.HalEventCallback callback) { 290 try { 291 Class helperClass = builtinContext.getClassLoader().loadClass( 292 BuiltinPackageDependency.EVS_HAL_WRAPPER_CLASS); 293 Constructor constructor = helperClass.getConstructor( 294 new Class[]{EvsHalWrapper.HalEventCallback.class}); 295 return (EvsHalWrapper) constructor.newInstance(callback); 296 } catch (Exception e) { 297 throw new RuntimeException( 298 "Cannot load class:" + BuiltinPackageDependency.EVS_HAL_WRAPPER_CLASS, e); 299 } 300 } 301 302 // Constructor StateMachine(Context context, Context builtinContext, CarEvsService service, ComponentName activityName, @CarEvsServiceType int type, String cameraId)303 StateMachine(Context context, Context builtinContext, CarEvsService service, 304 ComponentName activityName, @CarEvsServiceType int type, String cameraId) { 305 this(context, builtinContext, service, activityName, type, cameraId, /* handler= */ null); 306 } 307 StateMachine(Context context, Context builtinContext, CarEvsService service, ComponentName activityName, @CarEvsServiceType int type, String cameraId, Handler handler)308 StateMachine(Context context, Context builtinContext, CarEvsService service, 309 ComponentName activityName, @CarEvsServiceType int type, String cameraId, 310 Handler handler) { 311 String postfix = "." + CarEvsUtils.convertToString(type); 312 mLogTag = TAG_EVS + postfix; 313 mContext = context; 314 mCameraId = cameraId; 315 mActivityName = activityName; 316 if (DBG) { 317 Slogf.d(mLogTag, "Camera Activity=%s", mActivityName); 318 } 319 320 if (handler == null) { 321 mHandler = new Handler(mHandlerThread.getLooper()); 322 } else { 323 mHandler = handler; 324 } 325 326 mHalCallback = new HalCallback(); 327 mHalWrapper = StateMachine.createHalWrapper(builtinContext, mHalCallback); 328 mService = service; 329 mServiceType = type; 330 } 331 332 /***** Visible instance method section. *****/ 333 334 /** Initializes this StateMachine instance. */ init()335 boolean init() { 336 return mHalWrapper.init(); 337 } 338 339 /** Releases this StateMachine instance. */ release()340 void release() { 341 mHandler.removeCallbacks(mActivityRequestTimeoutRunnable); 342 mHalWrapper.release(); 343 } 344 345 /** 346 * Checks whether we are connected to the native EVS service. 347 * 348 * @return true if our connection to the native EVS service is valid. 349 * false otherwise. 350 */ isConnected()351 boolean isConnected() { 352 return mHalWrapper.isConnected(); 353 } 354 355 /** 356 * Sets a string camera identifier to use. 357 * 358 * @param id A string identifier of a target camera device. 359 */ setCameraId(String id)360 void setCameraId(String id) { 361 if (id.equalsIgnoreCase(DEFAULT_CAMERA_ALIAS)) { 362 mCameraIdOverride = mCameraId; 363 Slogf.i(TAG_EVS, "CarEvsService is set to use the default device for the rearview."); 364 } else { 365 mCameraIdOverride = id; 366 Slogf.i(TAG_EVS, "CarEvsService is set to use " + id + " for the rearview."); 367 } 368 } 369 370 /** 371 * Sets a string camera identifier to use. 372 * 373 * @return A camera identifier string we're going to use. 374 */ getCameraId()375 String getCameraId() { 376 return mCameraIdOverride != null ? mCameraIdOverride : mCameraId; 377 } 378 379 /** 380 * Notifies that we're done with a frame buffer associated with a given identifier. 381 * 382 * @param id An identifier of a frame buffer we have consumed. 383 */ doneWithFrame(int id)384 void doneWithFrame(int id) { 385 int bufferId = CarEvsUtils.getValue(id); 386 synchronized (mLock) { 387 int refcount = mBufferRecords.get(bufferId) - 1; 388 if (refcount > 0) { 389 if (DBG) { 390 Slogf.d(mLogTag, "Buffer %d has %d references.", id, refcount); 391 } 392 mBufferRecords.put(bufferId, refcount); 393 return; 394 } 395 396 mBufferRecords.delete(bufferId); 397 } 398 399 // This may throw a NullPointerException if the native EVS service handle is invalid. 400 mHalWrapper.doneWithFrame(bufferId); 401 } 402 403 /** 404 * Requests to start a registered activity with a given priority. 405 * 406 * @param priority A priority of current request; this should be either REQUEST_PRIORITY_HIGH or 407 * REQUEST_PRIORITY_NORMAL. 408 * 409 * @return ERROR_UNEVAILABLE if we are not initialized yet or we failed to connect to the native 410 * EVS service. 411 * ERROR_BUSY if a pending request has a higher priority. 412 * ERROR_NONE if no activity is registered or we succeed to request a registered 413 * activity. 414 */ requestStartActivity(int priority)415 @CarEvsError int requestStartActivity(int priority) { 416 if (mContext == null) { 417 Slogf.e(mLogTag, "Context is not valid."); 418 return ERROR_UNAVAILABLE; 419 } 420 421 if (mActivityName == null) { 422 Slogf.d(mLogTag, "No activity is set."); 423 return ERROR_NONE; 424 } 425 426 return execute(priority, SERVICE_STATE_REQUESTED); 427 } 428 429 /** 430 * Requests to start a registered activity if it is necessary. 431 * 432 * @return ERROR_UNEVAILABLE if we are not initialized yet or we failed to connect to the native 433 * EVS service. 434 * ERROR_BUSY if a pending request has a higher priority. 435 * ERROR_NONE if no activity is registered or we succeed to request a registered 436 * activity. 437 */ requestStartActivityIfNecessary()438 @CarEvsError int requestStartActivityIfNecessary() { 439 return startActivityIfNecessary(); 440 } 441 442 /** 443 * Requests to stop an activity. 444 * 445 * @param priority A priority of current request; this should be either REQUEST_PRIORITY_HIGH or 446 * REQUEST_PRIORITY_NORMAL. 447 * 448 * @return ERROR_NONE if no active streaming client exists, no activity has been registered, or 449 * current activity is successfully stopped. 450 * ERROR_UNAVAILABLE if we cannot connect to the native EVS service. 451 * ERROR_BUSY if current activity has a higher priority than a given priority. 452 */ requestStopActivity(int priority)453 @CarEvsError int requestStopActivity(int priority) { 454 if (mActivityName == null) { 455 Slogf.d(mLogTag, "Ignore a request to stop activity mActivityName=%s", mActivityName); 456 return ERROR_NONE; 457 } 458 459 stopActivity(); 460 return ERROR_NONE; 461 } 462 463 /** Requests to cancel a pending activity request. */ cancelActivityRequest()464 void cancelActivityRequest() { 465 if (mState != SERVICE_STATE_REQUESTED) { 466 return; 467 } 468 469 if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE) != ERROR_NONE) { 470 Slogf.w(mLogTag, "Failed to transition to INACTIVE state."); 471 } 472 } 473 474 /** Tries to connect to the EVS HAL service until it succeeds at a default interval. */ connectToHalServiceIfNecessary()475 void connectToHalServiceIfNecessary() { 476 connectToHalServiceIfNecessary(EVS_HAL_SERVICE_BIND_RETRY_INTERVAL_MS); 477 } 478 479 /** Shuts down the service and enters INACTIVE state. */ stopService()480 void stopService() { 481 // Stop all active clients. 482 mHalCallback.stop(); 483 } 484 485 /** 486 * Prioritizes video stream request and start a video stream. 487 * 488 * @param callback A callback to get frame buffers and stream events. 489 * @param token A token to recognize a client. If this is a valid session token, its owner will 490 * prioritized. 491 * 492 * @return ERROR_UNAVAILABLE if we're not connected to the native EVS service. 493 * ERROR_BUSY if current client has a higher priority. 494 * ERROR_NONE otherwise. 495 */ requestStartVideoStream(ICarEvsStreamCallback callback, IBinder token)496 @CarEvsError int requestStartVideoStream(ICarEvsStreamCallback callback, IBinder token) { 497 int priority; 498 if (isSessionToken(token)) { 499 // If a current request has a valid session token, we assume it comes from an activity 500 // launched by us for the high priority request. 501 mHandler.removeCallbacks(mActivityRequestTimeoutRunnable); 502 priority = REQUEST_PRIORITY_HIGH; 503 } else { 504 priority = REQUEST_PRIORITY_LOW; 505 } 506 507 return execute(priority, SERVICE_STATE_ACTIVE, token, callback); 508 } 509 510 /** 511 * Stops a video stream. 512 * 513 * @param callback A callback client who want to stop listening. 514 */ requestStopVideoStream(ICarEvsStreamCallback callback)515 void requestStopVideoStream(ICarEvsStreamCallback callback) { 516 if (!mHalCallback.contains(callback)) { 517 Slogf.d(mLogTag, "Ignores a video stream stop request not from current stream client."); 518 return; 519 } 520 521 if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE, callback) != ERROR_NONE) { 522 Slogf.w(mLogTag, "Failed to stop a video stream"); 523 } 524 } 525 526 /** 527 * Gets a current status of StateMachine. 528 * 529 * @return CarEvsServiceState that describes current state of a StateMachine instance. 530 */ getCurrentStatus()531 CarEvsStatus getCurrentStatus() { 532 synchronized (mLock) { 533 return new CarEvsStatus(CarEvsManager.SERVICE_TYPE_REARVIEW, mState); 534 } 535 } 536 537 /** 538 * Returns a String that describes a current session token. 539 */ 540 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)541 void dump(IndentingPrintWriter writer) { 542 synchronized (mLock) { 543 writer.printf("StateMachine 0x%s is providing %s.\n", 544 Integer.toHexString(System.identityHashCode(this)), 545 CarEvsUtils.convertToString(mServiceType)); 546 writer.printf("SessionToken = %s.\n", 547 mSessionToken == null ? "Not exist" : mSessionToken); 548 writer.increaseIndent(); 549 mHalCallback.dump(writer); 550 writer.decreaseIndent(); 551 writer.printf("\n"); 552 } 553 } 554 555 /** 556 * Confirms whether a given IBinder object is identical to current session token IBinder object. 557 * 558 * @param token IBinder object that a caller wants to examine. 559 * 560 * @return true if a given IBinder object is a valid session token. 561 * false otherwise. 562 */ isSessionToken(IBinder token)563 boolean isSessionToken(IBinder token) { 564 synchronized (mLock) { 565 return isSessionTokenLocked(token); 566 } 567 } 568 569 /** Handles client disconnections; may request to stop a video stream. */ handleClientDisconnected(ICarEvsStreamCallback callback)570 void handleClientDisconnected(ICarEvsStreamCallback callback) { 571 // If the last stream client is disconnected before it stops a video stream, request to stop 572 // current video stream. 573 execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE, callback); 574 } 575 576 /************************** Private methods ************************/ 577 execute(int priority, int destination)578 private @CarEvsError int execute(int priority, int destination) { 579 return execute(priority, destination, null, null); 580 } 581 582 execute(int priority, int destination, ICarEvsStreamCallback callback)583 private @CarEvsError int execute(int priority, int destination, 584 ICarEvsStreamCallback callback) { 585 return execute(priority, destination, null, callback); 586 } 587 588 /** 589 * Executes StateMachine to be in a requested state. 590 * 591 * @param priority A priority of current execution. 592 * @param destination A target service state we're desired to enter. 593 * @param token A session token IBinder object. 594 * @param callback A callback object we may need to work with. 595 * 596 * @return ERROR_NONE if we're already in a requested state. 597 * CarEvsError from each handler methods. 598 */ execute(int priority, int destination, IBinder token, ICarEvsStreamCallback callback)599 private @CarEvsError int execute(int priority, int destination, IBinder token, 600 ICarEvsStreamCallback callback) { 601 int result = ERROR_NONE; 602 int previousState, newState; 603 synchronized (mLock) { 604 previousState = mState; 605 Slogf.i(mLogTag, "Transition requested: %s -> %s", stateToString(previousState), 606 stateToString(destination)); 607 switch (destination) { 608 case SERVICE_STATE_UNAVAILABLE: 609 result = handleTransitionToUnavailableLocked(); 610 break; 611 612 case SERVICE_STATE_INACTIVE: 613 result = handleTransitionToInactiveLocked(priority, callback); 614 break; 615 616 case SERVICE_STATE_REQUESTED: 617 result = handleTransitionToRequestedLocked(priority); 618 break; 619 620 case SERVICE_STATE_ACTIVE: 621 result = handleTransitionToActiveLocked(priority, token, callback); 622 break; 623 624 default: 625 throw new IllegalStateException( 626 "CarEvsService is in the unknown state, " + previousState); 627 } 628 629 newState = mState; 630 } 631 632 if (result == ERROR_NONE) { 633 if (previousState != newState) { 634 Slogf.i(mLogTag, "Transition completed: %s", stateToString(destination)); 635 mService.broadcastStateTransition(CarEvsManager.SERVICE_TYPE_REARVIEW, newState); 636 } else { 637 Slogf.i(mLogTag, "Stay at %s", stateToString(newState)); 638 } 639 } else { 640 Slogf.e(mLogTag, "Transition failed: error = %d", result); 641 } 642 643 return result; 644 } 645 646 /** 647 * Checks conditions and tells whether we need to launch a registered activity. 648 * 649 * @return true if we should launch an activity. 650 * false otherwise. 651 */ needToStartActivity()652 private boolean needToStartActivity() { 653 if (mActivityName == null || mHandler.hasCallbacks(mActivityRequestTimeoutRunnable)) { 654 // No activity has been registered yet or it is already requested. 655 Slogf.d(mLogTag, 656 "No need to start an activity: mActivityName=%s, mHandler.hasCallbacks()=%s", 657 mActivityName, mHandler.hasCallbacks(mActivityRequestTimeoutRunnable)); 658 return false; 659 } 660 661 boolean startActivity = mService.needToStartActivity(); 662 synchronized (mLock) { 663 startActivity |= checkCurrentStateRequiresSystemActivityLocked(); 664 } 665 666 return startActivity; 667 } 668 669 /** 670 * Checks conditions and tells whether we need to launch a registered activity. 671 * 672 * @return true if we should launch an activity. 673 * false otherwise. 674 */ needToStartActivityLocked()675 private boolean needToStartActivityLocked() { 676 if (mActivityName == null || mHandler.hasCallbacks(mActivityRequestTimeoutRunnable)) { 677 // No activity has been registered yet or it is already requested. 678 Slogf.d(mLogTag, 679 "No need to start an activity: mActivityName=%s, mHandler.hasCallbacks()=%s", 680 mActivityName, mHandler.hasCallbacks(mActivityRequestTimeoutRunnable)); 681 return false; 682 } 683 684 return mService.needToStartActivity() || checkCurrentStateRequiresSystemActivityLocked(); 685 } 686 687 /** 688 * Launches a registered camera activity if necessary. 689 * 690 * @return ERROR_UNEVAILABLE if we are not initialized yet or we failed to connect to the native 691 * EVS service. 692 * ERROR_BUSY if a pending request has a higher priority. 693 * ERROR_NONE if no activity is registered or we succeed to request a registered 694 * activity. 695 */ startActivityIfNecessary()696 private @CarEvsError int startActivityIfNecessary() { 697 return startActivityIfNecessary(/* resetState= */ false); 698 } 699 700 /** 701 * Launches a registered activity if necessary. 702 * 703 * @param resetState when this is true, StateMachine enters INACTIVE state first and then moves 704 * into REQUESTED state. 705 * 706 * @return ERROR_UNEVAILABLE if we are not initialized yet or we failed to connect to the native 707 * EVS service. 708 * ERROR_BUSY if a pending request has a higher priority. 709 * ERROR_NONE if no activity is registered or we succeed to request a registered 710 * activity. 711 */ startActivityIfNecessary(boolean resetState)712 private @CarEvsError int startActivityIfNecessary(boolean resetState) { 713 if (!needToStartActivity()) { 714 // We do not need to start a camera activity. 715 return ERROR_NONE; 716 } 717 718 return startActivity(resetState); 719 } 720 721 /** 722 * Launches a registered activity. 723 * 724 * @param resetState when this is true, StateMachine enters INACTIVE state first and then moves 725 * into REQUESTED state. 726 * 727 * @return ERROR_UNEVAILABLE if we are not initialized yet or we failed to connect to the native 728 * EVS service. 729 * ERROR_BUSY if a pending request has a higher priority. 730 * ERROR_NONE if no activity is registered or we succeed to request a registered 731 * activity. 732 */ startActivity(boolean resetState)733 private @CarEvsError int startActivity(boolean resetState) { 734 // Request to launch an activity again after cleaning up. 735 int result = ERROR_NONE; 736 if (resetState) { 737 result = execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE); 738 if (result != ERROR_NONE) { 739 return result; 740 } 741 } 742 743 return execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_REQUESTED); 744 } 745 746 /** Stops a registered activity if it's running and enters INACTIVE state. */ stopActivity()747 private void stopActivity() { 748 IBinder token; 749 ICarEvsStreamCallback callback; 750 synchronized (mLock) { 751 token = mSessionToken; 752 callback = mPrivilegedCallback; 753 } 754 755 if (token == null || callback == null) { 756 Slogf.d(mLogTag, "No activity is running."); 757 return; 758 } 759 760 if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE, callback) != ERROR_NONE) { 761 Slogf.w(mLogTag, "Failed to stop a video stream"); 762 } 763 } 764 765 /** 766 * Try to connect to the EVS HAL service until it succeeds at a given interval. 767 * 768 * @param internalInMillis an interval to try again if current attempt fails. 769 */ connectToHalServiceIfNecessary(long intervalInMillis)770 private void connectToHalServiceIfNecessary(long intervalInMillis) { 771 if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE) != ERROR_NONE) { 772 // Try to restore a connection again after a given amount of time. 773 Slogf.i(TAG_EVS, "Failed to connect to EvsManager service. Retrying after %d ms.", 774 intervalInMillis); 775 mHandler.postDelayed(() -> connectToHalServiceIfNecessary(intervalInMillis), 776 intervalInMillis); 777 } 778 } 779 780 /** 781 * Notify the client of a video stream loss. 782 * 783 * @param callback A callback object we're about to stop forwarding frmae buffers and events. 784 */ notifyStreamStopped(ICarEvsStreamCallback callback)785 private void notifyStreamStopped(ICarEvsStreamCallback callback) { 786 if (callback == null) { 787 return; 788 } 789 790 try { 791 int taggedEvent = CarEvsUtils.putTag(mServiceType, 792 CarEvsManager.STREAM_EVENT_STREAM_STOPPED); 793 callback.onStreamEvent(taggedEvent); 794 } catch (RemoteException e) { 795 // Likely the binder death incident 796 Slogf.w(TAG_EVS, Log.getStackTraceString(e)); 797 } 798 } 799 800 /** 801 * Check whether or not a given token is a valid session token that can be used to prioritize 802 * requests. 803 * 804 * @param token A IBinder object a caller wants to confirm. 805 * 806 * @return true if a given IBinder object is a valid session token. 807 * false otherwise. 808 */ 809 @GuardedBy("mLock") isSessionTokenLocked(IBinder token)810 private boolean isSessionTokenLocked(IBinder token) { 811 return token != null && mService.isSessionToken(token); 812 } 813 814 /** 815 * Handle a transition from current state to UNAVAILABLE state. 816 * 817 * When the native EVS service becomes unavailable, CarEvsService notifies all active clients 818 * and enters UNAVAILABLE state. 819 * 820 * @return ERROR_NONE always. 821 */ 822 @GuardedBy("mLock") handleTransitionToUnavailableLocked()823 private @CarEvsError int handleTransitionToUnavailableLocked() { 824 // This transition happens only when CarEvsService loses the active connection to the 825 // Extended View System service. 826 switch (mState) { 827 case SERVICE_STATE_UNAVAILABLE: 828 // Nothing to do 829 break; 830 831 default: 832 // Stops any active video stream 833 stopService(); 834 break; 835 } 836 837 mState = SERVICE_STATE_UNAVAILABLE; 838 return ERROR_NONE; 839 } 840 841 /** 842 * Handle a transition from current state to INACTIVE state. 843 * 844 * INACTIVE state means that CarEvsService is connected to the EVS service and idles. 845 * 846 * @return ERROR_BUSY if CarEvsService is already busy with a higher priority client. 847 * ERROR_NONE otherwise. 848 */ 849 @GuardedBy("mLock") handleTransitionToInactiveLocked(int priority, ICarEvsStreamCallback callback)850 private @CarEvsError int handleTransitionToInactiveLocked(int priority, 851 ICarEvsStreamCallback callback) { 852 853 switch (mState) { 854 case SERVICE_STATE_UNAVAILABLE: 855 if (callback != null) { 856 // We get a request to stop a video stream after losing a native EVS 857 // service. Simply unregister a callback and return. 858 if (!mHalCallback.unregister(callback)) { 859 Slogf.d(mLogTag, "Ignored a request to unregister unknown callback %s", 860 callback); 861 } 862 return ERROR_NONE; 863 } else { 864 // Requested to connect to the Extended View System service 865 if (!mHalWrapper.connectToHalServiceIfNecessary()) { 866 return ERROR_UNAVAILABLE; 867 } 868 869 if (needToStartActivityLocked()) { 870 // Request to launch the viewer because we lost the Extended View System 871 // service while a client was actively streaming a video. 872 mHandler.postDelayed(mActivityRequestTimeoutRunnable, 873 STREAM_START_REQUEST_TIMEOUT_MS); 874 } 875 } 876 break; 877 878 case SERVICE_STATE_INACTIVE: 879 // Nothing to do 880 break; 881 882 case SERVICE_STATE_REQUESTED: 883 // Requested to cancel a pending service request 884 if (priority < mLastRequestPriority) { 885 return ERROR_BUSY; 886 } 887 888 // Reset a timer for this new request 889 mHandler.removeCallbacks(mActivityRequestTimeoutRunnable); 890 break; 891 892 case SERVICE_STATE_ACTIVE: 893 // Remove pending callbacks and notify a client. 894 if (callback != null) { 895 mHandler.postAtFrontOfQueue(() -> notifyStreamStopped(callback)); 896 if (!mHalCallback.unregister(callback)) { 897 Slogf.e(mLogTag, "Ignored a request to unregister unknown callback %s", 898 callback); 899 } 900 901 if (mPrivilegedCallback != null && 902 callback.asBinder() == mPrivilegedCallback.asBinder()) { 903 mPrivilegedCallback = null; 904 invalidateSessionTokenLocked(); 905 } 906 } 907 908 mHalWrapper.requestToStopVideoStream(); 909 if (!mHalCallback.isEmpty()) { 910 Slogf.i(mLogTag, "%s streaming client(s) is/are alive.", mHalCallback.size()); 911 return ERROR_NONE; 912 } 913 914 Slogf.i(mLogTag, "Last streaming client has been disconnected."); 915 mBufferRecords.clear(); 916 break; 917 918 default: 919 throw new IllegalStateException("CarEvsService is in the unknown state."); 920 } 921 922 mState = SERVICE_STATE_INACTIVE; 923 return ERROR_NONE; 924 } 925 926 /** 927 * Handle a transition from current state to REQUESTED state. 928 * 929 * CarEvsService enters this state when it is requested to launch a registered camera activity. 930 * 931 * @return ERROR_UNAVAILABLE if CarEvsService is not connected to the native EVS service. 932 * ERROR_BUSY if CarEvsService is processing a higher priority client. 933 * ERROR_NONE otherwise. 934 */ 935 @GuardedBy("mLock") handleTransitionToRequestedLocked(int priority)936 private @CarEvsError int handleTransitionToRequestedLocked(int priority) { 937 if (mActivityName == null) { 938 Slogf.e(mLogTag, "No activity is registered."); 939 return ERROR_UNAVAILABLE; 940 } 941 942 switch (mState) { 943 case SERVICE_STATE_UNAVAILABLE: 944 // Attempts to connect to the native EVS service and transits to the 945 // REQUESTED state if it succeeds. 946 if (!mHalWrapper.connectToHalServiceIfNecessary()) { 947 return ERROR_UNAVAILABLE; 948 } 949 break; 950 951 case SERVICE_STATE_INACTIVE: 952 // Nothing to do 953 break; 954 955 case SERVICE_STATE_REQUESTED: 956 if (priority < mLastRequestPriority) { 957 // A current service request has a lower priority than a previous 958 // service request. 959 Slogf.e(TAG_EVS, "CarEvsService is busy with a higher priority client."); 960 return ERROR_BUSY; 961 } 962 963 // Reset a timer for this new request if it exists. 964 mHandler.removeCallbacks(mActivityRequestTimeoutRunnable); 965 break; 966 967 case SERVICE_STATE_ACTIVE: 968 if (priority < mLastRequestPriority) { 969 // We decline a request because CarEvsService is busy with a higher priority 970 // client. 971 Slogf.e(TAG_EVS, "CarEvsService is busy with a higher priority client."); 972 return ERROR_BUSY; 973 } 974 break; 975 976 default: 977 throw new IllegalStateException("CarEvsService is in the unknown state."); 978 } 979 980 mState = SERVICE_STATE_REQUESTED; 981 mLastRequestPriority = priority; 982 983 Intent evsIntent = new Intent(Intent.ACTION_MAIN) 984 .setComponent(mActivityName) 985 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 986 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) 987 .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) 988 .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 989 990 // Stores a token and arms the timer for the high-priority request. 991 Bundle bundle = new Bundle(); 992 if (priority == REQUEST_PRIORITY_HIGH) { 993 bundle.putBinder(CarEvsManager.EXTRA_SESSION_TOKEN, 994 mService.generateSessionTokenInternal()); 995 mHandler.postDelayed( 996 mActivityRequestTimeoutRunnable, STREAM_START_REQUEST_TIMEOUT_MS); 997 } 998 // Temporary, we use CarEvsManager.SERVICE_TYPE_REARVIEW as the key for a service type 999 // value. 1000 bundle.putShort(Integer.toString(CarEvsManager.SERVICE_TYPE_REARVIEW), 1001 (short) mServiceType); 1002 evsIntent.replaceExtras(bundle); 1003 1004 mContext.startActivity(evsIntent); 1005 return ERROR_NONE; 1006 } 1007 1008 /** 1009 * Handle a transition from current state to ACTIVE state. 1010 * 1011 * @return ERROR_BUSY if CarEvsService is busy with a higher priority client. 1012 * ERROR_UNAVAILABLE if CarEvsService is in UNAVAILABLE state or fails to start a video 1013 * stream. 1014 * ERROR_NONE otherwise. 1015 */ 1016 @GuardedBy("mLock") handleTransitionToActiveLocked(int priority, IBinder token, ICarEvsStreamCallback callback)1017 private @CarEvsError int handleTransitionToActiveLocked(int priority, IBinder token, 1018 ICarEvsStreamCallback callback) { 1019 1020 @CarEvsError int result = ERROR_NONE; 1021 switch (mState) { 1022 case SERVICE_STATE_UNAVAILABLE: 1023 // We do not have a valid connection to the Extended View System service. 1024 if (!mHalWrapper.connectToHalServiceIfNecessary()) { 1025 return ERROR_UNAVAILABLE; 1026 } 1027 // fallthrough 1028 1029 case SERVICE_STATE_INACTIVE: 1030 // CarEvsService receives a low priority request to start a video stream. 1031 result = startService(); 1032 if (result != ERROR_NONE) { 1033 return result; 1034 } 1035 break; 1036 1037 case SERVICE_STATE_REQUESTED: 1038 // CarEvsService is reserved for higher priority clients. 1039 if (priority == REQUEST_PRIORITY_HIGH && !isSessionTokenLocked(token)) { 1040 // Declines a request with an expired token. 1041 return ERROR_BUSY; 1042 } 1043 1044 result = startService(); 1045 if (result != ERROR_NONE) { 1046 return result; 1047 } 1048 break; 1049 1050 case SERVICE_STATE_ACTIVE: 1051 // CarEvsManager will transfer an active video stream to a new client with a 1052 // higher or equal priority. 1053 if (priority < mLastRequestPriority) { 1054 Slogf.i(mLogTag, "Declines a service request with a lower priority."); 1055 break; 1056 } 1057 1058 result = startService(); 1059 if (result != ERROR_NONE) { 1060 return result; 1061 } 1062 break; 1063 1064 default: 1065 throw new IllegalStateException("CarEvsService is in the unknown state."); 1066 } 1067 1068 result = startVideoStream(callback, token); 1069 if (result == ERROR_NONE) { 1070 mState = SERVICE_STATE_ACTIVE; 1071 mLastRequestPriority = priority; 1072 if (isSessionTokenLocked(token)) { 1073 mSessionToken = token; 1074 mPrivilegedCallback = callback; 1075 } 1076 } 1077 return result; 1078 } 1079 1080 /** Connects to the native EVS service if necessary and opens a target camera device. */ startService()1081 private @CarEvsError int startService() { 1082 if (!mHalWrapper.connectToHalServiceIfNecessary()) { 1083 Slogf.e(mLogTag, "Failed to connect to EVS service."); 1084 return ERROR_UNAVAILABLE; 1085 } 1086 1087 String cameraId = mCameraIdOverride != null ? mCameraIdOverride : mCameraId; 1088 if (!mHalWrapper.openCamera(cameraId)) { 1089 Slogf.e(mLogTag, "Failed to open a targer camera device, %s", cameraId); 1090 return ERROR_UNAVAILABLE; 1091 } 1092 1093 return ERROR_NONE; 1094 } 1095 1096 /** Registers a callback and requests a video stream. */ startVideoStream(ICarEvsStreamCallback callback, IBinder token)1097 private @CarEvsError int startVideoStream(ICarEvsStreamCallback callback, IBinder token) { 1098 if (!mHalCallback.register(callback, token)) { 1099 Slogf.e(mLogTag, "Failed to set a stream callback."); 1100 return ERROR_UNAVAILABLE; 1101 } 1102 1103 if (!mHalWrapper.requestToStartVideoStream()) { 1104 Slogf.e(mLogTag, "Failed to start a video stream."); 1105 return ERROR_UNAVAILABLE; 1106 } 1107 1108 return ERROR_NONE; 1109 } 1110 1111 /** Waits for a video stream request from the System UI with a valid token. */ handleActivityRequestTimeout()1112 private void handleActivityRequestTimeout() { 1113 // No client has responded to a state transition to the REQUESTED 1114 // state before the timer expires. CarEvsService sends a 1115 // notification again if it's still needed. 1116 Slogf.d(mLogTag, "Timer expired. Request to launch the activity again."); 1117 if (startActivityIfNecessary(/* resetState= */ true) != ERROR_NONE) { 1118 Slogf.w(mLogTag, "Failed to request an activity."); 1119 } 1120 } 1121 1122 /** Invalidates current session token. */ 1123 @GuardedBy("mLock") invalidateSessionTokenLocked()1124 private void invalidateSessionTokenLocked() { 1125 mService.invalidateSessionToken(mSessionToken); 1126 mSessionToken = null; 1127 } 1128 1129 /** Checks whether or not we need to request a registered camera activity. */ 1130 @GuardedBy("mLock") checkCurrentStateRequiresSystemActivityLocked()1131 private boolean checkCurrentStateRequiresSystemActivityLocked() { 1132 return (mState == SERVICE_STATE_ACTIVE || mState == SERVICE_STATE_REQUESTED) && 1133 mLastRequestPriority == REQUEST_PRIORITY_HIGH; 1134 } 1135 1136 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) stateToString(@arEvsServiceState int state)1137 private String stateToString(@CarEvsServiceState int state) { 1138 switch (state) { 1139 case SERVICE_STATE_UNAVAILABLE: 1140 return "UNAVAILABLE"; 1141 case SERVICE_STATE_INACTIVE: 1142 return "INACTIVE"; 1143 case SERVICE_STATE_REQUESTED: 1144 return "REQUESTED"; 1145 case SERVICE_STATE_ACTIVE: 1146 return "ACTIVE"; 1147 default: 1148 return "UNKNOWN: " + state; 1149 } 1150 } 1151 1152 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) 1153 @Override toString()1154 public String toString() { 1155 synchronized (mLock) { 1156 return stateToString(mState); 1157 } 1158 } 1159 1160 /** Overrides a current state. */ 1161 @ExcludeFromCodeCoverageGeneratedReport(reason = DEBUGGING_CODE) 1162 @VisibleForTesting setState(@arEvsServiceState int newState)1163 void setState(@CarEvsServiceState int newState) { 1164 synchronized (mLock) { 1165 Slogf.d(mLogTag, "StateMachine(%s)'s state has been changed from %s to %s.", 1166 this, mState, newState); 1167 mState = newState; 1168 } 1169 } 1170 1171 /** Overrides a current callback object. */ 1172 @ExcludeFromCodeCoverageGeneratedReport(reason = DEBUGGING_CODE) 1173 @VisibleForTesting addStreamCallback(ICarEvsStreamCallback callback)1174 void addStreamCallback(ICarEvsStreamCallback callback) { 1175 Slogf.d(mLogTag, "Register additional callback %s", callback); 1176 mHalCallback.register(callback, /* token= */ null); 1177 } 1178 1179 /** Overrides a current valid session token. */ 1180 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) 1181 @VisibleForTesting setSessionToken(IBinder token)1182 void setSessionToken(IBinder token) { 1183 synchronized (mLock) { 1184 Slogf.d(mLogTag, "SessionToken %s is replaced with %s", mSessionToken, token); 1185 mSessionToken = token; 1186 } 1187 } 1188 } 1189