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