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