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