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