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