• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.evs;
18 
19 import static android.car.evs.CarEvsManager.ERROR_BUSY;
20 import static android.car.evs.CarEvsManager.ERROR_NONE;
21 import static android.car.evs.CarEvsManager.ERROR_UNAVAILABLE;
22 import static android.car.evs.CarEvsManager.SERVICE_STATE_ACTIVE;
23 import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
24 import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
25 import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
26 import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
27 
28 import static com.android.car.CarLog.TAG_EVS;
29 import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_LOW;
30 import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_NORMAL;
31 import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_HIGH;
32 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.car.Car;
37 import android.car.VehiclePropertyIds;
38 import android.car.builtin.content.pm.PackageManagerHelper;
39 import android.car.builtin.os.BuildHelper;
40 import android.car.builtin.util.Slogf;
41 import android.car.evs.CarEvsBufferDescriptor;
42 import android.car.evs.CarEvsManager;
43 import android.car.evs.CarEvsManager.CarEvsError;
44 import android.car.evs.CarEvsManager.CarEvsServiceState;
45 import android.car.evs.CarEvsManager.CarEvsServiceType;
46 import android.car.evs.CarEvsStatus;
47 import android.car.evs.ICarEvsStatusListener;
48 import android.car.evs.ICarEvsStreamCallback;
49 import android.car.hardware.CarPropertyValue;
50 import android.car.hardware.property.CarPropertyEvent;
51 import android.car.hardware.property.ICarPropertyEventListener;
52 import android.content.ComponentName;
53 import android.content.Context;
54 import android.content.pm.PackageManager.NameNotFoundException;
55 import android.hardware.automotive.vehicle.VehicleGear;
56 import android.hardware.display.DisplayManager;
57 import android.hardware.display.DisplayManager.DisplayListener;
58 import android.os.Binder;
59 import android.os.Bundle;
60 import android.os.Handler;
61 import android.os.IBinder;
62 import android.os.Looper;
63 import android.os.RemoteCallbackList;
64 import android.os.RemoteException;
65 import android.os.SystemClock;
66 import android.os.UserHandle;
67 import android.util.ArraySet;
68 import android.util.ArrayMap;
69 import android.util.Log;
70 import android.util.SparseArray;
71 import android.util.proto.ProtoOutputStream;
72 import android.view.Display;
73 
74 import com.android.car.CarPropertyService;
75 import com.android.car.CarServiceBase;
76 import com.android.car.CarServiceUtils;
77 import com.android.car.R;
78 import com.android.car.hal.EvsHalService;
79 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
80 import com.android.car.internal.evs.CarEvsUtils;
81 import com.android.car.internal.evs.EvsHalWrapper;
82 import com.android.car.internal.util.IndentingPrintWriter;
83 import com.android.internal.annotations.GuardedBy;
84 import com.android.internal.annotations.VisibleForTesting;
85 
86 import java.lang.ref.WeakReference;
87 import java.util.List;
88 import java.util.Objects;
89 import java.util.Set;
90 
91 /**
92  * A service that listens to the Extended View System across a HAL boundary and exposes the data to
93  * system clients in Android via {@link android.car.evs.CarEvsManager}.
94  *
95  * Because of Fast Message Queue usages, android.hardware.automotive.evs@1.1 interfaces does not
96  * support Java backend and, therefore, actual API calls are done in native methods.
97  *
98  *
99  * CarEvsService consists of four states:
100  *
101  * UNAVAILABLE: CarEvsService is not connected to the Extended View System service.  In this
102  * state, any service request will be declined.
103  *
104  * INACTIVE: CarEvsService has a valid, live connection the Extended View System service and
105  * ready for any service requests.
106  *
107  * REQUESTED: CarEvsService received a service requeste from a privileged client and requested
108  * the System UI to launch the camera viewing activity.
109  *
110  * ACTIVE: CarEvsService is actively streaming a video to the client.
111  *
112  * See CarEvsService.StateMachine class for more details.
113  */
114 public final class CarEvsService extends android.car.evs.ICarEvsService.Stub
115         implements CarServiceBase {
116 
117     private static final boolean DBG = Slogf.isLoggable(TAG_EVS, Log.DEBUG);
118 
119     static final class EvsHalEvent {
120         private long mTimestamp;
121         private int mServiceType;
122         private boolean mOn;
123 
EvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on)124         public EvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) {
125             mTimestamp = timestamp;
126             mServiceType = type;
127             mOn = on;
128         }
129 
getTimestamp()130         public long getTimestamp() {
131             return mTimestamp;
132         }
133 
getServiceType()134         public @CarEvsServiceType int getServiceType() {
135             return mServiceType;
136         }
137 
isRequestingToStartActivity()138         public boolean isRequestingToStartActivity() {
139             return mOn;
140         }
141 
142         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()143         public String toString() {
144             return "ServiceType=" + CarEvsUtils.convertToString(mServiceType) +
145                     ", mOn=" + mOn + ", Timestamp=" + mTimestamp;
146         }
147     }
148 
149     private final Context mContext;
150     private final Context mBuiltinContext;
151     private final EvsHalService mEvsHalService;
152     private final CarPropertyService mPropertyService;
153     private final DisplayManager mDisplayManager;  // To monitor the default display's state
154     private final Object mLock = new Object();
155     private final ArraySet<IBinder> mSessionTokens = new ArraySet<>();
156 
157     // This handler is to monitor the client sends a video stream request within a given time
158     // after a state transition to the REQUESTED state.
159     private final Handler mHandler = new Handler(Looper.getMainLooper());
160 
161     private final class StatusListenerList extends RemoteCallbackList<ICarEvsStatusListener> {
162         private final WeakReference<CarEvsService> mService;
163 
StatusListenerList(CarEvsService evsService)164         StatusListenerList(CarEvsService evsService) {
165             mService = new WeakReference<>(evsService);
166         }
167 
168         /** Handle callback death */
169         @Override
onCallbackDied(ICarEvsStatusListener listener)170         public void onCallbackDied(ICarEvsStatusListener listener) {
171             Slogf.w(TAG_EVS, "StatusListener has died: " + listener.asBinder());
172 
173             CarEvsService svc = mService.get();
174             if (svc != null) {
175                 svc.handleClientDisconnected(listener);
176             }
177         }
178     }
179 
180     private final StatusListenerList mStatusListeners = new StatusListenerList(this);
181 
182     /**
183      * {@link CarPropertyEvent} listener registered with {@link CarPropertyService} to listen to
184      * {@link VehicleProperty.GEAR_SELECTION} change notifications.
185      */
186     private final ICarPropertyEventListener mGearSelectionPropertyListener =
187             new ICarPropertyEventListener.Stub() {
188                 @Override
189                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
190                     if (events.isEmpty()) {
191                         return;
192                     }
193 
194                     // Handle only the latest event
195                     Slogf.i(TAG_EVS, "Handling GearSelection event");
196                     handlePropertyEvent(events.get(events.size() - 1));
197                 }
198             };
199 
200     private final DisplayListener mDisplayListener = new DisplayListener() {
201             @Override
202             public void onDisplayAdded(int displayId) {
203                 // Nothing to do
204             }
205 
206             @Override
207             public void onDisplayRemoved(int displayId) {
208                 // Nothing to do
209             }
210 
211             @Override
212             public void onDisplayChanged(int displayId) {
213                 if (displayId != Display.DEFAULT_DISPLAY) {
214                     // We are interested only in the default display.
215                     return;
216                 }
217 
218                 Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
219                 if (mCurrentDisplayState == display.getState()) {
220                     // We already handled this display state change.
221                     Slogf.i(TAG_EVS, "We already handled a reported display status, %d",
222                             display.getState());
223                     return;
224                 }
225 
226                 // TODO(b/292155786): Current implementation is optimized for the device with a
227                 //                    single display and therefore we may need to consider the
228                 //                    source of the display event and start/stop activities
229                 //                    accordingly.
230                 switch (display.getState()) {
231                     case Display.STATE_ON:
232                         // Requests each StateMachine to launch a registered activity if it's
233                         // necessary.
234                         for (int i = 0; i < mServiceInstances.size(); i++) {
235                             if (mServiceInstances.valueAt(i)
236                                     .requestStartActivityIfNecessary() == ERROR_NONE) {
237                                 continue;
238                             }
239                             Slogf.e(TAG_EVS, "Failed to start %s's activity.",
240                                     CarEvsUtils.convertToString(mServiceInstances.keyAt(i)));
241                         }
242                         break;
243 
244                     case Display.STATE_OFF:
245                         // Each StateMachine stores a valid session token that was used for
246                         // recognizing a streaming callback from a launched activity.
247                         // CarEvsService will request each StateMachine to stop those callbacks
248                         // and let other callbacks continue running. Activities not launched by
249                         // CarEvsService must handle display's state changes properly by
250                         // themselves.
251                         for (int i = 0; i < mServiceInstances.size(); i++) {
252                             mServiceInstances.valueAt(i)
253                                     .requestStopActivity(REQUEST_PRIORITY_HIGH);
254                         }
255                         break;
256 
257                     default:
258                         // Nothing to do for all other state changes
259                         break;
260                 }
261 
262                 mCurrentDisplayState = display.getState();
263             }
264         };
265 
266     // Service instances per each type.
267     private final SparseArray<StateMachine> mServiceInstances;
268 
269     // Associates callback objects with their service types.
270     private final ArrayMap<IBinder, ArraySet<Integer>> mCallbackToServiceType =
271             new ArrayMap<>();
272 
273     // The latest display state we have processed.
274     private int mCurrentDisplayState = Display.STATE_OFF;
275 
276     // This boolean flag is true if CarEvsService uses GEAR_SELECTION VHAL property instead of
277     // EVS_SERVICE_REQUEST.
278     private boolean mUseGearSelection = true;
279 
280     // The last event EvsHalService reported.  This will be set to null when a related service
281     // request is handled.
282     //
283     // To properly handle a HAL event that occurred before CarEvsService is ready, we initialize
284     // mLastEvsHalEvent with a zero timestamp here.
285     @GuardedBy("mLock")
286     private EvsHalEvent mLastEvsHalEvent = new EvsHalEvent(/* timestamp= */ 0,
287             CarEvsManager.SERVICE_TYPE_REARVIEW, /* on= */ false);
288 
289     /** Creates an Extended View System service instance given a {@link Context}. */
CarEvsService(Context context, Context builtinContext, EvsHalService halService, CarPropertyService propertyService)290     public CarEvsService(Context context, Context builtinContext, EvsHalService halService,
291             CarPropertyService propertyService) {
292         mContext = context;
293         mBuiltinContext = builtinContext;
294         mPropertyService = propertyService;
295         mEvsHalService = halService;
296 
297         // Reads the service configuration and initializes service instances.
298         String[] rawConfigurationStrings = mContext.getResources()
299                 .getStringArray(R.array.config_carEvsService);
300         if (rawConfigurationStrings != null && rawConfigurationStrings.length > 0) {
301             mServiceInstances = new SparseArray<>(rawConfigurationStrings.length);
302             for (String rawString : rawConfigurationStrings) {
303                 CarEvsServiceUtils.Parameters params = CarEvsServiceUtils.parse(rawString);
304 
305                 StateMachine s = new StateMachine(context, builtinContext, this,
306                         params.getActivityComponentName(), params.getType(), params.getCameraId());
307                 mServiceInstances.put(params.getType(), s);
308             }
309 
310             if (mServiceInstances.size() < 1) {
311                 Slogf.e(TAG_EVS, "No valid configuration has been found. " +
312                         "CarEvsService won't be available.");
313                 mDisplayManager = null;
314                 return;
315             }
316         } else {
317             mServiceInstances = new SparseArray<>(/* capacity= */ 1);
318             Slogf.i(TAG_EVS, "CarEvsService will be initialized only for the rearview service " +
319                              "because no service configuration was available via " +
320                              "config_carEvsService.");
321 
322             String activityName = mContext.getResources()
323                     .getString(R.string.config_evsCameraActivity);
324             ComponentName activityComponentName;
325             if (!activityName.isEmpty()) {
326                 activityComponentName = ComponentName.unflattenFromString(activityName);
327             } else {
328                 activityComponentName = null;
329             }
330             if (DBG) Slogf.d(TAG_EVS, "evsCameraActivity=" + activityName);
331 
332             String cameraId = context.getString(R.string.config_evsRearviewCameraId);
333             StateMachine s = new StateMachine(context, builtinContext, this, activityComponentName,
334                     CarEvsManager.SERVICE_TYPE_REARVIEW, cameraId);
335             mServiceInstances.put(CarEvsManager.SERVICE_TYPE_REARVIEW, s);
336         }
337 
338         mDisplayManager = context.getSystemService(DisplayManager.class);
339         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
340     }
341 
342     @VisibleForTesting
343     final class EvsTriggerListener implements EvsHalService.EvsHalEventListener {
344 
345         /** Implements EvsHalService.EvsHalEventListener to monitor VHAL properties. */
346         @Override
onEvent(@arEvsServiceType int type, boolean on)347         public void onEvent(@CarEvsServiceType int type, boolean on) {
348             if (DBG) {
349                 Slogf.d(TAG_EVS,
350                         "Received an event from EVS HAL: type = " + type + ", on = " + on);
351             }
352 
353             StateMachine instance = mServiceInstances.get(type);
354             if (instance == null) {
355                 Slogf.w(TAG_EVS, "CarEvsService is not configured for %s", type);
356                 return;
357             }
358 
359             // Stores the last event.
360             synchronized (mLock) {
361                 mLastEvsHalEvent = new EvsHalEvent(SystemClock.elapsedRealtimeNanos(), type, on);
362             }
363 
364             if (on) {
365                 // Request a camera activity.
366                 if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
367                     Slogf.e(TAG_EVS, "Fail to request a registered activity.");
368                 }
369             } else {
370                 // Stop a video stream and close an activity.
371                 if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
372                     Slogf.e(TAG_EVS, "Fail to stop a registered activity.");
373                 }
374             }
375         }
376     }
377 
378     @VisibleForTesting
379     final EvsTriggerListener mEvsTriggerListener = new EvsTriggerListener();
380 
381     @Override
init()382     public void init() {
383         if (DBG) {
384             Slogf.d(TAG_EVS, "Initializing the service");
385         }
386 
387         for (int i = mServiceInstances.size() - 1; i >= 0; i--) {
388             StateMachine instance = mServiceInstances.valueAt(i);
389             if (instance.init()) {
390                 continue;
391             }
392 
393             Slogf.e(TAG_EVS, "Failed to initialize a service handle for %s.",
394                     mServiceInstances.keyAt(i));
395             mServiceInstances.removeAt(i);
396         }
397 
398         if (mEvsHalService.isEvsServiceRequestSupported()) {
399             try {
400                 mEvsHalService.setListener(mEvsTriggerListener);
401                 if (DBG) {
402                     Slogf.d(TAG_EVS, "CarEvsService listens to EVS_SERVICE_REQUEST property.");
403                 }
404                 mUseGearSelection = false;
405             } catch (IllegalStateException e) {
406                 Slogf.w(TAG_EVS, "Failed to set a EvsHalService listener. Try to use "
407                         + "GEAR_SELECTION.");
408             }
409         }
410 
411         if (mUseGearSelection) {
412             if (mPropertyService == null || mPropertyService.getPropertySafe(
413                     VehiclePropertyIds.GEAR_SELECTION, /*areaId=*/ 0) == null) {
414                 Slogf.w(TAG_EVS,
415                         "GEAR_SELECTION property is also not available. " +
416                         "CarEvsService may not respond to the system events.");
417                 mUseGearSelection = false;
418             } else {
419                 if (DBG) {
420                     Slogf.d(TAG_EVS, "CarEvsService listens to GEAR_SELECTION property.");
421                 }
422 
423                 if (!mPropertyService.registerListenerSafe(
424                         VehiclePropertyIds.GEAR_SELECTION, /*updateRateHz=*/0,
425                         mGearSelectionPropertyListener)) {
426                     Slogf.w(TAG_EVS, "Failed to register a listener for GEAR_SELECTION property.");
427                     mUseGearSelection = false;
428                 }
429             }
430         }
431 
432         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
433         if (instance == null) {
434             Slogf.w(TAG_EVS, "The service is not initialized for the rearview service.");
435             return;
436         }
437 
438         instance.connectToHalServiceIfNecessary();
439     }
440 
441     @Override
release()442     public void release() {
443         if (DBG) {
444             Slogf.d(TAG_EVS, "Finalizing the service");
445         }
446 
447         if (mUseGearSelection && mPropertyService != null) {
448             if (DBG) {
449                 Slogf.d(TAG_EVS, "Unregister a property listener in release()");
450             }
451             mPropertyService.unregisterListenerSafe(VehiclePropertyIds.GEAR_SELECTION,
452                     mGearSelectionPropertyListener);
453         }
454 
455         for (int i = 0; i < mServiceInstances.size(); i++) {
456             StateMachine instance = mServiceInstances.valueAt(i);
457             instance.release();
458         }
459 
460         mStatusListeners.kill();
461     }
462 
463     @Override
464     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)465     public void dump(IndentingPrintWriter writer) {
466         writer.println("*CarEvsService*");
467 
468         writer.increaseIndent();
469         for (int i = 0; i < mServiceInstances.size(); i++) {
470             mServiceInstances.valueAt(i).dump(writer);
471         }
472         writer.decreaseIndent();
473         writer.printf("\n");
474 
475         synchronized (mLock) {
476             writer.printf("%d service listeners subscribed.\n",
477                     mStatusListeners.getRegisteredCallbackCount());
478             writer.printf("Last HAL event: %s\n", mLastEvsHalEvent);
479         }
480     }
481 
482     /**
483      * Registers a {@link ICarEvsStatusListener} to listen requests to control the camera
484      * previewing activity.
485      *
486      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
487      * access.
488      *
489      * @param listener {@link ICarEvsStatusListener} listener to register.
490      */
491     @Override
registerStatusListener(@onNull ICarEvsStatusListener listener)492     public void registerStatusListener(@NonNull ICarEvsStatusListener listener) {
493         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
494         Objects.requireNonNull(listener);
495 
496         if (DBG) {
497             Slogf.d(TAG_EVS, "Registering a new service listener");
498         }
499         mStatusListeners.register(listener);
500     }
501 
502     /**
503      * Unregister the given {@link ICarEvsStatusListener} listener from receiving events.
504      *
505      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
506      * access.
507      *
508      * @param listener {@link ICarEvsStatusListener} listener to unregister.
509      */
510     @Override
unregisterStatusListener(@onNull ICarEvsStatusListener listener)511     public void unregisterStatusListener(@NonNull ICarEvsStatusListener listener) {
512         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
513         Objects.requireNonNull(listener);
514 
515         mStatusListeners.unregister(listener);
516     }
517 
518     /**
519      * Requests the system to start an activity to show the preview from a given EVS service type.
520      *
521      * <p>Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to
522      * access.
523      *
524      * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType}
525      * @return {@link android.car.evs.CarEvsManager#CarEvsError}
526      */
527     @Override
startActivity(int type)528     public @CarEvsError int startActivity(int type) {
529         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY);
530 
531         if (type == CarEvsManager.SERVICE_TYPE_SURROUNDVIEW) {
532             // TODO(b/179029031): Removes below when Surround View service is integrated.
533             Slogf.e(TAG_EVS, "Surround view is not supported yet.");
534             return ERROR_UNAVAILABLE;
535         }
536 
537         StateMachine instance = mServiceInstances.get(type);
538         if (instance == null) {
539             return ERROR_UNAVAILABLE;
540         }
541 
542         return instance.requestStartActivity(REQUEST_PRIORITY_NORMAL);
543     }
544 
545     /**
546      * Requests to stop a current previewing activity launched via {@link #startActivity}.
547      *
548      * <p>Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to
549      * access.
550      */
551     @Override
stopActivity()552     public void stopActivity() {
553         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY);
554 
555         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
556         if (instance == null) {
557             return;
558         }
559 
560         instance.requestStopActivity(REQUEST_PRIORITY_NORMAL);
561     }
562 
563     /**
564      * Starts a video stream.
565      *
566      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
567      *
568      * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType}
569      * @param token IBinder object as a session token.  If this is not null, CarEvsService handles a
570      *              coming client as a privileged client.
571      * @param callback {@link ICarEvsStreamCallback} listener to register.
572      * @return {@link android.car.evs.CarEvsManager.CarEvsError}
573      */
574     @Override
startVideoStream(@arEvsServiceType int type, @Nullable IBinder token, @NonNull ICarEvsStreamCallback callback)575     public @CarEvsError int startVideoStream(@CarEvsServiceType int type, @Nullable IBinder token,
576             @NonNull ICarEvsStreamCallback callback) {
577         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
578         Objects.requireNonNull(callback);
579 
580         StateMachine instance = mServiceInstances.get(type);
581         if (instance == null) {
582             Slogf.e(TAG_EVS, "CarEvsService is not configured for a service type %d.", type);
583             return ERROR_UNAVAILABLE;
584         }
585 
586         // Single client can subscribe to multiple services.
587         // ArrayMap<IBinder, ArraySet<Integer>>
588         // Remembers which service a given callback is subscribing to.
589         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
590         if (types == null) {
591             mCallbackToServiceType.put(callback.asBinder(),
592                     new ArraySet<>(Set.of(new Integer(type))));
593         } else {
594             types.add(type);
595         }
596 
597         return instance.requestStartVideoStream(callback, token);
598     }
599 
600     /**
601      * Requests to stop a video stream from the current client.
602      *
603      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
604      *
605      * @param callback {@link ICarEvsStreamCallback} listener to unregister.
606      */
607     @Override
stopVideoStream(@onNull ICarEvsStreamCallback callback)608     public void stopVideoStream(@NonNull ICarEvsStreamCallback callback) {
609         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
610         Objects.requireNonNull(callback);
611 
612         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
613         if (types == null || types.isEmpty()) {
614             Slogf.i(TAG_EVS, "Ignores a request to stop a video stream for unknown callback %s.",
615                     callback);
616             return;
617         }
618 
619         for (int i = 0; i < types.size(); i++) {
620             int type = types.valueAt(i);
621             StateMachine instance = mServiceInstances.get(type);
622             if (instance == null) {
623                 Slogf.w(TAG_EVS, "CarEvsService is not configured for a service type %d.", type);
624                 continue;
625             }
626 
627             instance.requestStopVideoStream(callback);
628         }
629         mCallbackToServiceType.remove(callback.asBinder());
630     }
631 
632     /**
633      * Returns an used buffer to EVS service.
634      *
635      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
636      *
637      * @param buffer A consumed CarEvsBufferDescriptor object.  This would not be used and returned
638      *               to the native EVS service.
639      * @throws IllegalArgumentException if a passed buffer has an unregistered identifier.
640      */
641     @Override
returnFrameBuffer(@onNull CarEvsBufferDescriptor buffer)642     public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
643         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
644         Objects.requireNonNull(buffer);
645 
646         // 8 MSB tells the service type of this buffer.
647         @CarEvsServiceType int type = CarEvsUtils.getTag(buffer.getId());
648         mServiceInstances.get(type).doneWithFrame(buffer.getId());
649     }
650 
651     /**
652      * Returns a current status of CarEvsService's REARVIEW service type.
653      *
654      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
655      * access.
656      *
657      * @return {@link android.car.evs.CarEvsStatus}
658      */
659     @Override
660     @Nullable
getCurrentStatus()661     public CarEvsStatus getCurrentStatus() {
662         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
663 
664         // This public API only returns current status of SERVICE_TYPE_REARVIEW. To get other
665         // services' status, please register a status listener via
666         // CarEvsService.registerStatusListener() API.
667         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
668         if (instance == null) {
669             return null;
670         }
671 
672         return instance.getCurrentStatus();
673     }
674 
675     /**
676      * Returns a session token to be used to request the services.
677      *
678      * <p>Requires {@link android.car.Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY} permission to access.
679      *
680      * @return IBinder object as a session token.
681      * @throws IllegalStateException if we fail to find System UI package.
682      */
683     @Override
generateSessionToken()684     public IBinder generateSessionToken() {
685         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY);
686 
687         // TODO(b/191940626): With the unlimited multi-client supports, a validity of a session
688         //                    token does not make any different in handling streaming clients. This
689         //                    needs to be modified with a logic to manage the maximum number of
690         //                    streaming clients per service.
691         String systemUiPackageName = PackageManagerHelper.getSystemUiPackageName(mContext);
692         IBinder token = new Binder();
693         try {
694             int systemUiUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(),
695                     systemUiPackageName, UserHandle.SYSTEM.getIdentifier());
696             int callerUid = Binder.getCallingUid();
697             if (systemUiUid == callerUid) {
698                 mSessionTokens.add(token);
699             } else {
700                 throw new SecurityException("SystemUI only can generate SessionToken");
701             }
702         } catch (NameNotFoundException e) {
703             throw new IllegalStateException(systemUiPackageName + " package not found", e);
704         } finally {
705             return token;
706         }
707     }
708 
709     /**
710      * Returns whether or not a given service type is supported.
711      *
712      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
713      * access.
714      */
715     @Override
isSupported(@arEvsServiceType int type)716     public boolean isSupported(@CarEvsServiceType int type) {
717         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
718 
719         StateMachine instance = mServiceInstances.get(type);
720         if (instance == null) {
721             return false;
722         }
723 
724         return instance.isConnected();
725     }
726 
727     /**
728      * Sets a camera device for the rearview.
729      *
730      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
731      *
732      * @param id A string identifier of a target camera device.
733      * @return This method return a false if this runs in a release build; otherwise, this returns
734      *         true.
735      */
setRearviewCameraIdFromCommand(@onNull String id)736     public boolean setRearviewCameraIdFromCommand(@NonNull String id) {
737         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
738         Objects.requireNonNull(id);
739 
740         if (!BuildHelper.isDebuggableBuild()) {
741             // This method is not allowed in the release build.
742             Slogf.e(TAG_EVS, "It is not allowed to change a camera assigned to the rearview " +
743                     "in the release build.");
744             return false;
745         }
746 
747         mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW).setCameraId(id);
748         return true;
749     }
750 
751     /**
752      * Sets a camera device for a given service type.
753      *
754      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
755      *
756      * @param type A service type to assign a camera associated with a given string identifier.
757      *             Please use '*' part of CarEvsManager.SERVICE_TYPE_* constants.
758      * @param id A string identifier of a target camera device.
759      * @return This method return true if it successfully programs a camera id for a given service
760      *         type. Otherwise, this will return false.
761      */
setCameraIdFromCommand(@onNull String type, @NonNull String id)762     public boolean setCameraIdFromCommand(@NonNull String type, @NonNull String id) {
763         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
764 
765         if (!BuildHelper.isDebuggableBuild()) {
766             // This method is not allowed in the release build.
767             Slogf.e(TAG_EVS, "It is not allowed to change a camera id assigned to the service " +
768                     "in the release build.");
769             return false;
770         }
771 
772         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
773         StateMachine instance = mServiceInstances.get(serviceType);
774         if (instance == null) {
775             Slogf.e(TAG_EVS, "Ignores a request to set a camera %s for unavailable service %s.",
776                     id, type);
777             return false;
778         }
779 
780         instance.setCameraId(id);
781         return true;
782     }
783 
784     /**
785      * Gets an identifier of a current camera device for the rearview.
786      *
787      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
788      * access.
789      *
790      * @return A string identifier of current rearview camera device.
791      */
792     @NonNull
getRearviewCameraIdFromCommand()793     public String getRearviewCameraIdFromCommand() {
794         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
795         return mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW).getCameraId();
796     }
797 
798     /**
799      * Gets a String identifier of a camera assigned to a given service type.
800      *
801      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
802      * access.
803      *
804      * @param type A service type to get a camera identifier. Please use "*" part of
805      *             CarEvsManager.SERVICE_TYPE_* constants.
806      * @return A string identifier of a camera assigned to a given service type.
807      */
808     @Nullable
getCameraIdFromCommand(@onNull String type)809     public String getCameraIdFromCommand(@NonNull String type) {
810         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
811         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
812         StateMachine instance = mServiceInstances.get(serviceType);
813         if (instance == null) {
814             return null;
815         }
816 
817         return instance.getCameraId();
818     }
819 
820     /**
821      * Enables a given service type with a specified camera device.
822      *
823      * <p>Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access.
824      *
825      * @param typeString A service type to get a camera identifier. Please use "*" part of
826      *             CarEvsManager.SERVICE_TYPE_* constants.
827      * @param cameraId A string identifier of a target camera device. A camera associated with this
828      *                 id must not be assigned to any service type.
829      * @return false if a requested service type is already enabled or a specific camera id is
830      *               already assigned to other service types.
831      *         true otherwise.
832      */
enableServiceTypeFromCommand(@onNull String typeString, @NonNull String cameraId)833     public boolean enableServiceTypeFromCommand(@NonNull String typeString,
834             @NonNull String cameraId) {
835         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
836 
837         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(typeString);
838         for (int i = 0; i < mServiceInstances.size(); i++) {
839             int type = mServiceInstances.keyAt(i);
840             StateMachine instance = mServiceInstances.valueAt(i);
841 
842             if (type == serviceType || cameraId.equals(instance.getCameraId())) {
843                 Slogf.e(TAG_EVS, "A requested service type is already provided by " +
844                         " or a given camera id is used by %s.", instance);
845                 return false;
846             }
847         }
848 
849         StateMachine s = new StateMachine(mContext, mBuiltinContext, this, null,
850                 serviceType, cameraId);
851         mServiceInstances.put(serviceType, s);
852         return true;
853     }
854 
855     /**
856      * Checks whether or not a given service type is enabled.
857      *
858      * <p>Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to
859      * access.
860      *
861      * @param type A service type to get a camera identifier. Please use "*" part of
862      *             CarEvsManager.SERVICE_TYPE_* constants.
863      * @return true if a given service type is available.
864      *         false otherwise.
865      */
isServiceTypeEnabledFromCommand(@onNull String type)866     public boolean isServiceTypeEnabledFromCommand(@NonNull String type) {
867         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
868         @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
869         return mServiceInstances.get(serviceType) != null;
870     }
871 
872     /** Checks whether or not a given token is valid. */
isSessionToken(IBinder token)873     boolean isSessionToken(IBinder token) {
874         return mSessionTokens.contains(token);
875     }
876 
877     /** Invalidate a given token. */
invalidateSessionToken(IBinder token)878     void invalidateSessionToken(IBinder token) {
879         mSessionTokens.remove(token);
880     }
881 
882     /** Package-private version of generateSessionToken() method. */
883     @NonNull
generateSessionTokenInternal()884     IBinder generateSessionTokenInternal() {
885         IBinder token = new Binder();
886         mSessionTokens.add(token);
887         return token;
888     }
889 
890     /**
891      * Manually sets a stream callback.
892      */
893     @VisibleForTesting
addStreamCallback(@arEvsServiceType int type, @Nullable ICarEvsStreamCallback callback)894     void addStreamCallback(@CarEvsServiceType int type, @Nullable ICarEvsStreamCallback callback) {
895         StateMachine instance = mServiceInstances.get(type);
896         if (instance == null || callback == null) {
897             return;
898         }
899 
900         instance.addStreamCallback(callback);
901 
902         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
903         if (types == null) {
904             mCallbackToServiceType.put(callback.asBinder(),
905                     new ArraySet<>(Set.of(new Integer(type))));
906         } else {
907             types.add(type);
908         }
909     }
910 
911     /** Tells whether or not the latest EVS HAL event was requesting to start an activity. */
needToStartActivity()912     boolean needToStartActivity() {
913         synchronized (mLock) {
914             return mLastEvsHalEvent != null && mLastEvsHalEvent.isRequestingToStartActivity();
915         }
916     }
917 
918     /**
919      * Manually sets a current service state.
920      */
921     @VisibleForTesting
setServiceState(@arEvsServiceType int type, @CarEvsServiceState int newState)922     void setServiceState(@CarEvsServiceType int type, @CarEvsServiceState int newState) {
923         StateMachine instance = mServiceInstances.get(type);
924         if (instance == null) {
925             return;
926         }
927 
928         instance.setState(newState);
929     }
930 
931     /**
932      * Manually chooses to use a gear selection property or not.
933      */
934     @VisibleForTesting
setToUseGearSelection(boolean useGearSelection)935     void setToUseGearSelection(boolean useGearSelection) {
936         mUseGearSelection = useGearSelection;
937     }
938 
939     /**
940      * Manually sets the last EVS HAL event.
941      */
942     @VisibleForTesting
setLastEvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on)943     void setLastEvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) {
944         synchronized (mLock) {
945             mLastEvsHalEvent = new EvsHalEvent(timestamp, type, on);
946         }
947     }
948 
949     /** Handles client disconnections; may request to stop a video stream. */
handleClientDisconnected(ICarEvsStreamCallback callback)950     private void handleClientDisconnected(ICarEvsStreamCallback callback) {
951         // If the last stream client is disconnected before it stops a video stream, request to stop
952         // current video stream.
953         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
954         if (types == null) {
955             Slogf.d(TAG_EVS, "Ignores an incidental loss of unknown callback %s.",
956                     callback.asBinder());
957             return;
958         }
959 
960         for (int i = 0; i < types.size(); i++) {
961             StateMachine instance = mServiceInstances.get(types.valueAt(i));
962             if (instance == null) {
963                 Slogf.i(TAG_EVS, "Ignores an incidental loss of a callback %s for service %d.",
964                         callback.asBinder(), types.valueAt(i));
965                 return;
966             }
967 
968             instance.handleClientDisconnected(callback);
969         }
970     }
971 
972     /** Notifies the service status gets changed */
broadcastStateTransition(int type, int state)973     void broadcastStateTransition(int type, int state) {
974         int idx = mStatusListeners.beginBroadcast();
975         while (idx-- > 0) {
976             ICarEvsStatusListener listener = mStatusListeners.getBroadcastItem(idx);
977             try {
978                 listener.onStatusChanged(new CarEvsStatus(type, state));
979             } catch (RemoteException e) {
980                 // Likely the binder death incident.
981                 Slogf.e(TAG_EVS, Log.getStackTraceString(e));
982             }
983         }
984         mStatusListeners.finishBroadcast();
985     }
986 
987     /** Stops a current service */
stopService()988     void stopService() {
989         stopService(/* callback= */ null);
990     }
991 
stopService(ICarEvsStreamCallback callback)992     private void stopService(ICarEvsStreamCallback callback) {
993         ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
994         if (types == null || types.isEmpty()) {
995             Slogf.d(TAG_EVS, "Ignores a request to stop a service for unknown callback %s.",
996                     callback.asBinder());
997             return;
998         }
999 
1000         for (int i = 0; i < types.size(); i++) {
1001             StateMachine instance = mServiceInstances.get(types.valueAt(i));
1002             if (instance == null) {
1003                 Slogf.i(TAG_EVS, "Ignores a request to stop unsupported service %d.",
1004                         types.valueAt(i));
1005                 return;
1006             }
1007 
1008             instance.requestStopVideoStream(callback);
1009         }
1010     }
1011 
handlePropertyEvent(CarPropertyEvent event)1012     private void handlePropertyEvent(CarPropertyEvent event) {
1013         if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
1014             // CarEvsService is interested only in the property change event.
1015             return;
1016         }
1017 
1018         CarPropertyValue value = event.getCarPropertyValue();
1019         if (value.getPropertyId() != VehiclePropertyIds.GEAR_SELECTION) {
1020             // CarEvsService is interested only in the GEAR_SELECTION property.
1021             return;
1022         }
1023 
1024         long timestamp = value.getTimestamp();
1025         boolean isReverseGear;
1026         synchronized (mLock) {
1027             if (timestamp != 0 && timestamp <= mLastEvsHalEvent.getTimestamp()) {
1028                 if (DBG) {
1029                     Slogf.d(TAG_EVS,
1030                             "Ignoring GEAR_SELECTION change happened past, timestamp = " +
1031                             timestamp + ", last event was at " + mLastEvsHalEvent.getTimestamp());
1032                 }
1033                 return;
1034             }
1035 
1036 
1037             isReverseGear = (Integer) value.getValue() == VehicleGear.GEAR_REVERSE;
1038             mLastEvsHalEvent = new EvsHalEvent(timestamp, CarEvsManager.SERVICE_TYPE_REARVIEW,
1039                     isReverseGear);
1040         }
1041 
1042         StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
1043         if (instance == null) {
1044             Slogf.i(TAG_EVS,
1045                     "Ignore a GEAR_SELECTION event because the rearview service is not available.");
1046             return;
1047         }
1048 
1049         if (isReverseGear) {
1050             // Request to start the rearview activity when the gear is shifted into the reverse
1051             // position.
1052             if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
1053                 Slogf.w(TAG_EVS, "Failed to request the rearview activity.");
1054             }
1055         } else {
1056             // Request to stop the rearview activity when the gear is shifted from the reverse
1057             // position to other positions.
1058             if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) {
1059                 Slogf.i(TAG_EVS, "Failed to stop the rearview activity.");
1060             }
1061         }
1062     }
1063 
1064     /** Notify the client of a video stream loss */
notifyStreamStopped(@onNull ICarEvsStreamCallback callback)1065     private static void notifyStreamStopped(@NonNull ICarEvsStreamCallback callback) {
1066         Objects.requireNonNull(callback);
1067 
1068         try {
1069             callback.onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
1070         } catch (RemoteException e) {
1071             // Likely the binder death incident
1072             Slogf.w(TAG_EVS, Log.getStackTraceString(e));
1073         }
1074     }
1075 
1076     /** Handles a disconnection of a status monitoring client. */
handleClientDisconnected(ICarEvsStatusListener listener)1077     private void handleClientDisconnected(ICarEvsStatusListener listener) {
1078         mStatusListeners.unregister(listener);
1079         if (mStatusListeners.getRegisteredCallbackCount() == 0) {
1080             Slogf.d(TAG_EVS, "Last status listener has been disconnected.");
1081         }
1082     }
1083 }
1084