• 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 android.car.evs;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SuppressLint;
25 import android.annotation.SystemApi;
26 import android.car.Car;
27 import android.car.CarManagerBase;
28 import android.car.annotation.RequiredFeature;
29 import android.os.Binder;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.util.Log;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.GuardedBy;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.lang.ref.WeakReference;
40 import java.util.Objects;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * Provides an application interface for interativing with the Extended View System service.
45  *
46  * @hide
47  */
48 @RequiredFeature(Car.CAR_EVS_SERVICE)
49 @SystemApi
50 public final class CarEvsManager extends CarManagerBase {
51     public static final String EXTRA_SESSION_TOKEN = "android.car.evs.extra.SESSION_TOKEN";
52 
53     private static final String TAG = CarEvsManager.class.getSimpleName();
54     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
55 
56     private final ICarEvsService mService;
57     private final Object mStreamLock = new Object();
58 
59     @GuardedBy("mStreamLock")
60     private CarEvsStreamCallback mStreamCallback;
61 
62     @GuardedBy("mStreamLock")
63     private Executor mStreamCallbackExecutor;
64 
65     private final CarEvsStreamListenerToService mStreamListenerToService =
66             new CarEvsStreamListenerToService(this);
67 
68     private final Object mStatusLock = new Object();
69 
70     @GuardedBy("mStatusLock")
71     private CarEvsStatusListener mStatusListener;
72 
73     @GuardedBy("mStatusLock")
74     private Executor mStatusListenerExecutor;
75 
76     private final CarEvsStatusListenerToService mStatusListenerToService =
77             new CarEvsStatusListenerToService(this);
78 
79     /**
80      * Service type to represent the rearview camera service.
81      */
82     public static final int SERVICE_TYPE_REARVIEW = 0;
83 
84     /**
85      * Service type to represent the surround view service.
86      */
87     public static final int SERVICE_TYPE_SURROUNDVIEW = 1;
88 
89     /** @hide */
90     @IntDef (prefix = {"SERVICE_TYPE_"}, value = {
91             SERVICE_TYPE_REARVIEW,
92             SERVICE_TYPE_SURROUNDVIEW,
93     })
94     @Retention(RetentionPolicy.SOURCE)
95     public @interface CarEvsServiceType {}
96 
97     /**
98      * State that a corresponding service type is not available.
99      */
100     public static final int SERVICE_STATE_UNAVAILABLE = 0;
101 
102     /**
103      * State that a corresponding service type is inactive; it's available but not used
104      * by any clients.
105      */
106     public static final int SERVICE_STATE_INACTIVE = 1;
107 
108     /**
109      * State that CarEvsManager received a service request from the client.
110      */
111     public static final int SERVICE_STATE_REQUESTED = 2;
112 
113     /**
114      * State that a corresponding service type is actively being used.
115      */
116     public static final int SERVICE_STATE_ACTIVE = 3;
117 
118     /** @hide */
119     @IntDef (prefix = {"SERVICE_STATE_"}, value = {
120             SERVICE_STATE_UNAVAILABLE,
121             SERVICE_STATE_INACTIVE,
122             SERVICE_STATE_REQUESTED,
123             SERVICE_STATE_ACTIVE
124     })
125     @Retention(RetentionPolicy.SOURCE)
126     public @interface CarEvsServiceState {}
127 
128     /**
129      * This is a default EVS stream event type.
130      */
131     public static final int STREAM_EVENT_NONE = 0;
132 
133     /**
134      * EVS stream event to notify a video stream has been started.
135      */
136     public static final int STREAM_EVENT_STREAM_STARTED = 1;
137 
138     /**
139      * EVS stream event to notify a video stream has been stopped.
140      */
141     public static final int STREAM_EVENT_STREAM_STOPPED = 2;
142 
143     /**
144      * EVS stream event to notify that a video stream is dropped.
145      */
146     public static final int STREAM_EVENT_FRAME_DROPPED = 3;
147 
148     /**
149      * EVS stream event occurs when a timer for a new frame's arrival is expired.
150      */
151     public static final int STREAM_EVENT_TIMEOUT = 4;
152 
153     /**
154      * EVS stream event occurs when a camera parameter is changed.
155      */
156     public static final int STREAM_EVENT_PARAMETER_CHANGED = 5;
157 
158     /**
159      * EVS stream event to notify the primary owner has been changed.
160      */
161     public static final int STREAM_EVENT_PRIMARY_OWNER_CHANGED = 6;
162 
163     /**
164      * Other EVS stream errors
165      */
166     public static final int STREAM_EVENT_OTHER_ERRORS = 7;
167 
168     /** @hide */
169     @IntDef(prefix = {"STREAM_EVENT_"}, value = {
170         STREAM_EVENT_NONE,
171         STREAM_EVENT_STREAM_STARTED,
172         STREAM_EVENT_STREAM_STOPPED,
173         STREAM_EVENT_FRAME_DROPPED,
174         STREAM_EVENT_TIMEOUT,
175         STREAM_EVENT_PARAMETER_CHANGED,
176         STREAM_EVENT_PRIMARY_OWNER_CHANGED,
177         STREAM_EVENT_OTHER_ERRORS
178     })
179     @Retention(RetentionPolicy.SOURCE)
180     public @interface CarEvsStreamEvent {}
181 
182     /**
183      * Status to tell that a request is successfully processed.
184      */
185     public static final int ERROR_NONE = 0;
186 
187     /**
188      * Status to tell a requested service is not available.
189      */
190     public static final int ERROR_UNAVAILABLE = -1;
191 
192     /**
193      * Status to tell CarEvsService is busy to serve the privileged client.
194      */
195     public static final int ERROR_BUSY = -2;
196 
197     /** @hide */
198     @IntDef(prefix = {"ERROR_"}, value = {
199         ERROR_NONE,
200         ERROR_UNAVAILABLE,
201         ERROR_BUSY
202     })
203     @Retention(RetentionPolicy.SOURCE)
204     public @interface CarEvsError {}
205 
206     /**
207      * Gets an instance of CarEvsManager
208      *
209      * CarEvsManager manages {@link com.android.car.evs.CarEvsService} and provides APIs that the
210      * clients can use the Extended View System service.
211      *
212      * This must not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
213      *
214      * @hide
215      */
CarEvsManager(Car car, IBinder service)216     public CarEvsManager(Car car, IBinder service) {
217         super(car);
218 
219         // Gets CarEvsService
220         mService = ICarEvsService.Stub.asInterface(service);
221     }
222 
223     /** @hide */
224     @Override
onCarDisconnected()225     public void onCarDisconnected() {
226         synchronized (mStatusLock) {
227             mStatusListener = null;
228             mStatusListenerExecutor = null;
229         }
230 
231         synchronized (mStreamLock) {
232             mStreamCallback = null;
233             mStreamCallbackExecutor = null;
234         }
235     }
236 
237     /**
238      * Application registers {@link #CarEvsStatusListener} object to receive requests to control
239      * the activity and monitor the status of the EVS service.
240      */
241     public interface CarEvsStatusListener {
242         /**
243          * Called when the status of EVS service is changed.
244          *
245          * @param type A type of EVS service; e.g. the rearview.
246          * @param state Updated service state; e.g. the service is started.
247          */
onStatusChanged(@onNull CarEvsStatus status)248         void onStatusChanged(@NonNull CarEvsStatus status);
249     }
250 
251     /**
252      * Class implementing the listener interface {@link com.android.car.ICarEvsStatusListener}
253      * to listen status updates across the binder interface.
254      */
255     private static class CarEvsStatusListenerToService extends ICarEvsStatusListener.Stub {
256         private final WeakReference<CarEvsManager> mManager;
257 
CarEvsStatusListenerToService(CarEvsManager manager)258         CarEvsStatusListenerToService(CarEvsManager manager) {
259             mManager = new WeakReference<>(manager);
260         }
261 
262         @Override
onStatusChanged(@onNull CarEvsStatus status)263         public void onStatusChanged(@NonNull CarEvsStatus status) {
264             Objects.requireNonNull(status);
265 
266             CarEvsManager mgr = mManager.get();
267             if (mgr != null) {
268                 mgr.handleServiceStatusChanged(status);
269             }
270         }
271     }
272 
273     /**
274      * Gets the {@link #CarEvsStatus} from the service listener {@link
275      * #CarEvsStatusListenerToService} and forwards it to the client.
276      *
277      * @param status {@link android.car.evs.CarEvsStatus}
278      */
handleServiceStatusChanged(CarEvsStatus status)279     private void handleServiceStatusChanged(CarEvsStatus status) {
280         if (DBG) {
281             Slog.d(TAG, "Service state changed: service = " + status.getServiceType() +
282                     ", state = " + status.getState());
283         }
284 
285         final CarEvsStatusListener listener;
286         final Executor executor;
287         synchronized (mStatusLock) {
288             listener = mStatusListener;
289             executor = mStatusListenerExecutor;
290         }
291 
292         if (listener != null) {
293             executor.execute(() -> listener.onStatusChanged(status));
294         } else if (DBG) {
295             Slog.w(TAG, "No client seems active; a received event is ignored.");
296         }
297     }
298 
299     /**
300      * Sets {@link #CarEvsStatusListener} object to receive requests to control the activity
301      * view and EVS data.
302      *
303      * @param executor {@link java.util.concurrent.Executor} to execute callbacks.
304      * @param listener {@link #CarEvsStatusListener} to register.
305      * @throws IllegalStateException if this method is called while a registered status listener
306      *         exists.
307      */
308     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
setStatusListener(@onNull @allbackExecutor Executor executor, @NonNull CarEvsStatusListener listener)309     public void setStatusListener(@NonNull @CallbackExecutor Executor executor,
310             @NonNull CarEvsStatusListener listener) {
311         if (DBG) {
312             Slog.d(TAG, "Registering a service monitoring listener.");
313         }
314 
315         Objects.requireNonNull(listener);
316         Objects.requireNonNull(executor);
317 
318         if (mStatusListener != null) {
319             throw new IllegalStateException("A status listener is already registered.");
320         }
321 
322         synchronized (mStatusLock) {
323             mStatusListener = listener;
324             mStatusListenerExecutor = executor;
325         }
326 
327         try {
328             mService.registerStatusListener(mStatusListenerToService);
329         } catch (RemoteException err) {
330             handleRemoteExceptionFromCarService(err);
331         }
332     }
333 
334     /**
335      * Stops getting callbacks to control the camera viewing activity by clearing
336      * {@link #CarEvsStatusListener} object.
337      */
338     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
clearStatusListener()339     public void clearStatusListener() {
340         if (DBG) {
341             Slog.d(TAG, "Unregistering a service monitoring callback.");
342         }
343 
344         synchronized (mStatusLock) {
345             mStatusListener = null;
346         }
347 
348         try{
349             mService.unregisterStatusListener(mStatusListenerToService);
350         } catch (RemoteException err) {
351             handleRemoteExceptionFromCarService(err);
352         }
353     }
354 
355     /**
356      * Application registers {@link #CarEvsStreamCallback} object to listen to EVS services' status
357      * changes.
358      *
359      * CarEvsManager supports two client types; one is a System UI type client and another is a
360      * normal Android activity type client.  The former client type has a priority over
361      * the latter type client and CarEvsManager allows only a single client of each type to
362      * subscribe.
363      */
364     // TODO(b/174572385): Removes below lint suppression
365     @SuppressLint("CallbackInterface")
366     public interface CarEvsStreamCallback {
367         /**
368          * Called when any EVS stream events occur.
369          *
370          * @param event {@link #CarEvsStreamEvent}; e.g. a stream started
371          */
onStreamEvent(@arEvsStreamEvent int event)372         default void onStreamEvent(@CarEvsStreamEvent int event) {}
373 
374         /**
375          * Called when new frame arrives.
376          *
377          * @param buffer {@link android.car.evs.CarEvsBufferDescriptor} contains a EVS frame
378          */
onNewFrame(@onNull CarEvsBufferDescriptor buffer)379         default void onNewFrame(@NonNull CarEvsBufferDescriptor buffer) {}
380     }
381 
382     /**
383      * Class implementing the listener interface and gets callbacks from the
384      * {@link com.android.car.ICarEvsStreamCallback} across the binder interface.
385      */
386     private static class CarEvsStreamListenerToService extends ICarEvsStreamCallback.Stub {
387         private final WeakReference<CarEvsManager> mManager;
388 
CarEvsStreamListenerToService(CarEvsManager manager)389         CarEvsStreamListenerToService(CarEvsManager manager) {
390             mManager = new WeakReference<>(manager);
391         }
392 
393         @Override
onStreamEvent(@arEvsStreamEvent int event)394         public void onStreamEvent(@CarEvsStreamEvent int event) {
395             CarEvsManager manager = mManager.get();
396             if (manager != null) {
397                 manager.handleStreamEvent(event);
398             }
399         }
400 
401         @Override
onNewFrame(CarEvsBufferDescriptor buffer)402         public void onNewFrame(CarEvsBufferDescriptor buffer) {
403             CarEvsManager manager = mManager.get();
404             if (manager != null) {
405                 manager.handleNewFrame(buffer);
406             }
407         }
408     }
409 
410     /**
411      * Gets the {@link #CarEvsStreamEvent} from the service listener
412      * {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
413      * to the manager.
414      *
415      * @param event {@link #CarEvsStreamEvent} from the service this manager subscribes to.
416      */
handleStreamEvent(@arEvsStreamEvent int event)417     private void handleStreamEvent(@CarEvsStreamEvent int event) {
418         if (DBG) {
419             Slog.d(TAG, "Received: " + event);
420         }
421 
422         final CarEvsStreamCallback callback;
423         final Executor executor;
424         synchronized (mStreamLock) {
425             callback = mStreamCallback;
426             executor = mStreamCallbackExecutor;
427         }
428 
429         if (callback != null) {
430             executor.execute(() -> callback.onStreamEvent(event));
431         } else if (DBG) {
432             Slog.w(TAG, "No client seems active; a current stream event is ignored.");
433         }
434     }
435 
436     /**
437      * Gets the {@link android.car.evs.CarEvsBufferDescriptor} from the service listener
438      * {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
439      * to the manager.
440      *
441      * @param buffer {@link android.car.evs.CarEvsBufferDescriptor}
442      */
handleNewFrame(@onNull CarEvsBufferDescriptor buffer)443     private void handleNewFrame(@NonNull CarEvsBufferDescriptor buffer) {
444         Objects.requireNonNull(buffer);
445         if (DBG) {
446             Slog.d(TAG, "Received a buffer: " + buffer);
447         }
448 
449         final CarEvsStreamCallback callback;
450         final Executor executor;
451         synchronized (mStreamLock) {
452             callback = mStreamCallback;
453             executor = mStreamCallbackExecutor;
454         }
455 
456         if (callback != null) {
457             executor.execute(() -> callback.onNewFrame(buffer));
458         } else {
459             if (DBG) {
460                 Slog.w(TAG, "A buffer is being returned back to the service " +
461                         "because no active clients exist.");
462             }
463             returnFrameBuffer(buffer);
464         }
465     }
466 
467     /**
468      * Returns a consumed {@link android.car.evs.CarEvsBufferDescriptor}.
469      *
470      * @param buffer {@link android.car.evs.CarEvsBufferDescriptor} to be returned to
471      * the EVS service.
472      */
473     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
returnFrameBuffer(@onNull CarEvsBufferDescriptor buffer)474     public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
475         Objects.requireNonNull(buffer);
476         try {
477             mService.returnFrameBuffer(buffer.getId());
478         } catch (RemoteException err) {
479             handleRemoteExceptionFromCarService(err);
480         }
481     }
482 
483     /**
484      * Requests the system to start an activity for {@link #CarEvsServiceType}.
485      *
486      * @param type A type of EVS service to start.
487      * @return {@link #CarEvsError} to tell the result of the request.
488      *         {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
489      *         the native EVS service or the binder transaction fails.
490      *         {@link #ERROR_BUSY} will be returned if the CarEvsService is in the
491      *         {@link #SERVICE_STATE_REQUESTED} for a different service type.
492      *         If the same service type is running, this will return {@link #ERROR_NONE}.
493      *         {@link #ERROR_NONE} will be returned for all other cases.
494      */
495     @RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
startActivity(@arEvsServiceType int type)496     public @CarEvsError int startActivity(@CarEvsServiceType int type) {
497         try {
498             return mService.startActivity(type);
499         } catch (RemoteException err) {
500             handleRemoteExceptionFromCarService(err);
501         }
502 
503         return ERROR_UNAVAILABLE;
504     }
505 
506     /**
507      * Requests the system to stop a current activity launched via {@link #startActivity}.
508      */
509     @RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
stopActivity()510     public void stopActivity() {
511         try {
512             mService.stopActivity();
513         } catch (RemoteException err) {
514             handleRemoteExceptionFromCarService(err);
515         }
516     }
517 
518     /**
519      * Requests to start a video stream from {@link #CarEvsServiceType}.
520      *
521      * @param type A type of EVS service.
522      * @param token A session token that is issued to privileged clients.  SystemUI must obtain this
523      *        token obtain this via {@link #generateSessionToken} and pass it to the activity, to
524      *        prioritize its service requests.
525      *        TODO(b/179517136): Defines an Intent extra
526      * @param callback {@link #CarEvsStreamCallback} to listen to the stream.
527      * @param executor {@link java.util.concurrent.Executor} to run a callback.
528      * @return {@link #CarEvsError} to tell the result of the request.
529      *         {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
530      *         the native EVS service or the binder transaction fails.
531      *         {@link #ERROR_BUSY} will be returned if the CarEvsService is handling a service
532      *         request with a valid session token.
533      *         {@link #ERROR_NONE} for all other cases.
534      */
535     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
startVideoStream( @arEvsServiceType int type, @Nullable IBinder token, @NonNull @CallbackExecutor Executor executor, @NonNull CarEvsStreamCallback callback)536     public @CarEvsError int startVideoStream(
537             @CarEvsServiceType int type,
538             @Nullable IBinder token,
539             @NonNull @CallbackExecutor Executor executor,
540             @NonNull CarEvsStreamCallback callback) {
541         if (DBG) {
542             Slog.d(TAG, "Received a request to start a video stream: " + type);
543         }
544 
545         Objects.requireNonNull(executor);
546         Objects.requireNonNull(callback);
547 
548         synchronized (mStreamLock) {
549             mStreamCallback = callback;
550             mStreamCallbackExecutor = executor;
551         }
552 
553         int status = ERROR_UNAVAILABLE;
554         try {
555             // Requests the service to start a video stream
556             status = mService.startVideoStream(type, token, mStreamListenerToService);
557         } catch (RemoteException err) {
558             handleRemoteExceptionFromCarService(err);
559         } finally {
560             return status;
561         }
562     }
563 
564     /**
565      * Requests to stop a current {@link #CarEvsServiceType}.
566      */
567     @RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
stopVideoStream()568     public void stopVideoStream() {
569         synchronized (mStreamLock) {
570             if (mStreamCallback == null) {
571                 Slog.e(TAG, "The service has not started yet.");
572                 return;
573             }
574 
575             // We're not interested in frames and events anymore.  The client can safely assume
576             // the service is stopped properly.
577             mStreamCallback = null;
578             mStreamCallbackExecutor = null;
579         }
580 
581         try {
582             mService.stopVideoStream(mStreamListenerToService);
583         } catch (RemoteException err) {
584             handleRemoteExceptionFromCarService(err);
585         }
586     }
587 
588     /**
589      * Queries the current status of CarEvsService
590      *
591      * @return {@link android.car.evs.CarEvsStatus} that describes current status of
592      * CarEvsService.
593      */
594     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
595     @NonNull
getCurrentStatus()596     public CarEvsStatus getCurrentStatus() {
597         try {
598             return mService.getCurrentStatus();
599         } catch (RemoteException err) {
600             Slog.e(TAG, "Failed to read a status of the service.");
601             return new CarEvsStatus(SERVICE_TYPE_REARVIEW, SERVICE_STATE_UNAVAILABLE);
602         }
603     }
604 
605     /**
606      * Generates a service session token.
607      *
608      * @return {@link IBinder} object as a service session token.
609      */
610     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY)
611     @NonNull
generateSessionToken()612     public IBinder generateSessionToken() {
613         IBinder token = null;
614         try {
615             token =  mService.generateSessionToken();
616             if (token == null) {
617                 token = new Binder();
618             }
619         } catch (RemoteException err) {
620             Slog.e(TAG, "Failed to generate a session token.");
621             token = new Binder();
622         } finally {
623             return token;
624         }
625 
626     }
627 
628     /**
629      * Returns whether or not a given service type is supported.
630      *
631      * @param type {@link CarEvsServiceType} to query
632      * @return true if a given service type is available on the system.
633      */
634     @RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
isSupported(@arEvsServiceType int type)635     public boolean isSupported(@CarEvsServiceType int type) {
636         try {
637             return mService.isSupported(type);
638         } catch (RemoteException err) {
639             Slog.e(TAG, "Failed to query a service availability");
640             return false;
641         }
642     }
643 }
644