1 /*
2  * Copyright (C) 2020 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 package com.android.car;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import android.car.occupantawareness.IOccupantAwarenessEventCallback;
21 import android.car.occupantawareness.OccupantAwarenessDetection;
22 import android.car.occupantawareness.SystemStatusEvent;
23 import android.content.Context;
24 import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
25 import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
26 import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus;
27 import android.hardware.automotive.occupant_awareness.OccupantDetections;
28 import android.hardware.automotive.occupant_awareness.Role;
29 import android.os.RemoteException;
30 
31 import androidx.test.core.app.ApplicationProvider;
32 import androidx.test.filters.MediumTest;
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.util.concurrent.CountDownLatch;
41 import java.util.concurrent.TimeUnit;
42 
43 @RunWith(AndroidJUnit4.class)
44 @MediumTest
45 public final class OccupantAwarenessServiceTest {
46     /**
47      * Mock implementation of {@link
48      * android.hardware.automotive.occupant_awareness.IOccupantAwareness} for testing the service
49      * and manager.
50      */
51     private static final class MockOasHal
52             extends android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub {
53         private IOccupantAwarenessClientCallback mCallback;
54         private boolean mGraphIsRunning;
55 
MockOasHal()56         MockOasHal() {}
57 
58         /** Returns whether the mock graph is running. */
isGraphRunning()59         public boolean isGraphRunning() {
60             return mGraphIsRunning;
61         }
62 
63         @Override
getLatestDetection(OccupantDetections detections)64         public void getLatestDetection(OccupantDetections detections) {}
65 
66         @Override
setCallback(IOccupantAwarenessClientCallback callback)67         public void setCallback(IOccupantAwarenessClientCallback callback) {
68             mCallback = callback;
69         }
70 
71         @Override
getState(int occupantRole, int detectionCapability)72         public @OccupantAwarenessStatus byte getState(int occupantRole, int detectionCapability) {
73             return OccupantAwarenessStatus.READY;
74         }
75 
76         @Override
startDetection()77         public @OccupantAwarenessStatus byte startDetection() {
78             mGraphIsRunning = true;
79             return OccupantAwarenessStatus.READY;
80         }
81 
82         @Override
stopDetection()83         public @OccupantAwarenessStatus byte stopDetection() {
84             mGraphIsRunning = false;
85             return OccupantAwarenessStatus.READY;
86         }
87 
88         @Override
getCapabilityForRole(@ole int occupantRole)89         public int getCapabilityForRole(@Role int occupantRole) {
90             if (occupantRole == OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER) {
91                 return SystemStatusEvent.DETECTION_TYPE_PRESENCE
92                         | SystemStatusEvent.DETECTION_TYPE_GAZE
93                         | SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING;
94             } else if (occupantRole
95                     == OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER) {
96                 return SystemStatusEvent.DETECTION_TYPE_PRESENCE;
97             } else {
98                 return SystemStatusEvent.DETECTION_TYPE_NONE;
99             }
100         }
101 
102         /** Causes a status event to be generated with the specified flags. */
fireStatusEvent(int detectionFlags, @OccupantAwarenessStatus byte status)103         public void fireStatusEvent(int detectionFlags, @OccupantAwarenessStatus byte status)
104                 throws RemoteException {
105             if (mCallback != null) {
106                 mCallback.onSystemStatusChanged(detectionFlags, status);
107             }
108         }
109 
110         @Override
getInterfaceVersion()111         public int getInterfaceVersion() {
112             return this.VERSION;
113         }
114 
115         @Override
getInterfaceHash()116         public String getInterfaceHash() {
117             return this.HASH;
118         }
119     }
120 
121     private MockOasHal mMockHal;
122     private com.android.car.OccupantAwarenessService mOasService;
123 
124     private CountDownLatch mStatusLatch;
125     private SystemStatusEvent mSystemStatus;
126 
127     @Before
setUp()128     public void setUp() {
129         Context context = ApplicationProvider.getApplicationContext();
130         mMockHal = new MockOasHal();
131         mOasService = new com.android.car.OccupantAwarenessService(context, mMockHal);
132         mOasService.init();
133 
134         resetStatus();
135     }
136 
137     @After
tearDown()138     public void tearDown() {
139         mOasService.release();
140     }
141 
142     @Test
testWithNoRegisteredListeners()143     public void testWithNoRegisteredListeners() throws Exception {
144         // Verify operation when no listeners are registered.
145         mMockHal.fireStatusEvent(IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.READY);
146 
147         // Give some time for the asynchronous event to be propagated
148         pauseForEventPropagation();
149 
150         // Nothing should have been received.
151         assertThat(mSystemStatus).isNull();
152     }
153 
154     @Test
testRegisterEventListener_returnsSystemStatusReady()155     public void testRegisterEventListener_returnsSystemStatusReady() throws Exception {
156         // Fire a status event and ensure it is received.
157         // "Presence status is ready"
158         IOccupantAwarenessEventCallback callback = registerCallbackToService();
159         mMockHal.fireStatusEvent(
160                 IOccupantAwareness.CAP_PRESENCE_DETECTION, OccupantAwarenessStatus.READY);
161         waitStatusReady();
162 
163         assertThat(mSystemStatus.detectionType)
164                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_PRESENCE);
165         assertThat(mSystemStatus.systemStatus).isEqualTo(SystemStatusEvent.SYSTEM_STATUS_READY);
166 
167         mOasService.unregisterEventListener(callback);
168     }
169 
170     @Test
testRegisterEventListener_returnsSystemStatusFailure()171     public void testRegisterEventListener_returnsSystemStatusFailure() throws Exception {
172         // "Gaze status is failed"
173         IOccupantAwarenessEventCallback callback = registerCallbackToService();
174         mMockHal.fireStatusEvent(
175                 IOccupantAwareness.CAP_GAZE_DETECTION, OccupantAwarenessStatus.FAILURE);
176         waitStatusReady();
177 
178         assertThat(mSystemStatus.detectionType).isEqualTo(SystemStatusEvent.DETECTION_TYPE_GAZE);
179         assertThat(mSystemStatus.systemStatus)
180                 .isEqualTo(SystemStatusEvent.SYSTEM_STATUS_SYSTEM_FAILURE);
181 
182         mOasService.unregisterEventListener(callback);
183     }
184 
185     @Test
testRegisterEventListener_returnsSystemStatusNotReady()186     public void testRegisterEventListener_returnsSystemStatusNotReady() throws Exception {
187         // "Driver monitoring status is not-ready"
188         IOccupantAwarenessEventCallback callback = registerCallbackToService();
189         mMockHal.fireStatusEvent(
190                 IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION,
191                 OccupantAwarenessStatus.NOT_INITIALIZED);
192         waitStatusReady();
193 
194         assertThat(mSystemStatus.detectionType)
195                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING);
196         assertThat(mSystemStatus.systemStatus)
197                 .isEqualTo(SystemStatusEvent.SYSTEM_STATUS_NOT_READY);
198 
199         mOasService.unregisterEventListener(callback);
200     }
201 
202     @Test
testRegisterEventListener_returnsSystemStatusNotSupported()203     public void testRegisterEventListener_returnsSystemStatusNotSupported() throws Exception {
204         // "None is non-supported"
205         IOccupantAwarenessEventCallback callback = registerCallbackToService();
206         mMockHal.fireStatusEvent(
207                 IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.NOT_SUPPORTED);
208         waitStatusReady();
209 
210         assertThat(mSystemStatus.detectionType).isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
211         assertThat(mSystemStatus.systemStatus)
212                 .isEqualTo(SystemStatusEvent.SYSTEM_STATUS_NOT_SUPPORTED);
213 
214         mOasService.unregisterEventListener(callback);
215     }
216 
217     @Test
test_unregisteredListeners()218     public void test_unregisteredListeners() throws Exception {
219         // Verify that listeners are successfully unregistered.
220         IOccupantAwarenessEventCallback callback = registerCallbackToService();
221 
222         // Unregister the registered listener.
223         mOasService.unregisterEventListener(callback);
224 
225         // Fire some events.
226         mMockHal.fireStatusEvent(IOccupantAwareness.CAP_NONE, OccupantAwarenessStatus.READY);
227         mMockHal.fireStatusEvent(
228                 IOccupantAwareness.CAP_GAZE_DETECTION, OccupantAwarenessStatus.READY);
229         mMockHal.fireStatusEvent(
230                 IOccupantAwareness.CAP_DRIVER_MONITORING_DETECTION, OccupantAwarenessStatus.READY);
231 
232         // Give some time for the asynchronous event to be propagated
233         pauseForEventPropagation();
234 
235         // Nothing should have been received.
236         assertThat(mSystemStatus).isNull();
237 
238         // Unregister a second time should log an error, but otherwise not cause any action.
239         mOasService.unregisterEventListener(callback);
240     }
241 
242     @Test
testGetCapabilityForRole_returnsAggregatedDriverStatus()243     public void testGetCapabilityForRole_returnsAggregatedDriverStatus() throws Exception {
244         assertThat(mOasService.getCapabilityForRole(
245                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER))
246                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_PRESENCE
247                         | SystemStatusEvent.DETECTION_TYPE_GAZE
248                         | SystemStatusEvent.DETECTION_TYPE_DRIVER_MONITORING);
249     }
250 
251     @Test
testGetCapabilityForRole_returnsPresence()252     public void testGetCapabilityForRole_returnsPresence() throws Exception {
253         assertThat(mOasService.getCapabilityForRole(
254                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_FRONT_PASSENGER))
255                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_PRESENCE);
256     }
257 
258     @Test
testGetCapabilityForRole_returnsNone_withRow2PassengerLeft()259     public void testGetCapabilityForRole_returnsNone_withRow2PassengerLeft() throws Exception {
260         assertThat(mOasService.getCapabilityForRole(
261                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_LEFT))
262                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
263     }
264 
265     @Test
testGetCapabilityForRole_returnsNone_withRow2PassengerCenter()266     public void testGetCapabilityForRole_returnsNone_withRow2PassengerCenter() throws Exception {
267         assertThat(mOasService.getCapabilityForRole(
268                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_CENTER))
269                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
270     }
271 
272     @Test
testGetCapabilityForRole_returnsNone_withRow2PassengerRight()273     public void testGetCapabilityForRole_returnsNone_withRow2PassengerRight() throws Exception {
274         assertThat(mOasService.getCapabilityForRole(
275                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_ROW_2_PASSENGER_RIGHT))
276                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
277     }
278 
279     @Test
testGetCapabilityForRole_returnsNone_withOccupantNone()280     public void testGetCapabilityForRole_returnsNone_withOccupantNone() throws Exception {
281         assertThat(mOasService.getCapabilityForRole(
282                 OccupantAwarenessDetection.VEHICLE_OCCUPANT_NONE))
283                 .isEqualTo(SystemStatusEvent.DETECTION_TYPE_NONE);
284     }
285 
286     @Test
testRegisterEventListener_returnsGraphNotRunningOnStart()287     public void testRegisterEventListener_returnsGraphNotRunningOnStart()
288             throws Exception {
289         // Should be not running on start (no clients are yet connected).
290         assertThat(mMockHal.isGraphRunning()).isFalse();
291     }
292 
293     @Test
testRegisterEventListener_returnsGraphRunningWithOneListener()294     public void testRegisterEventListener_returnsGraphRunningWithOneListener()
295             throws Exception {
296         // Connect a client. Graph should be running.
297         IOccupantAwarenessEventCallback first_client = registerCallbackToService();
298 
299         assertThat(mMockHal.isGraphRunning()).isTrue();
300 
301         mOasService.unregisterEventListener(first_client);
302     }
303 
304     @Test
testRegisterEventListener_returnsGraphRunningWithTwoListeners()305     public void testRegisterEventListener_returnsGraphRunningWithTwoListeners()
306             throws Exception {
307         // Connect multiple (two) clients. Graph should be running.
308         IOccupantAwarenessEventCallback first_client = registerCallbackToService();
309         IOccupantAwarenessEventCallback second_client = registerCallbackToService();
310 
311         assertThat(mMockHal.isGraphRunning()).isTrue();
312 
313         mOasService.unregisterEventListener(first_client);
314         mOasService.unregisterEventListener(second_client);
315         assertThat(mMockHal.isGraphRunning()).isFalse();
316     }
317 
318     @Test
testUnregisterEventListener_returnsGraphNotRunningWithoutListeners()319     public void testUnregisterEventListener_returnsGraphNotRunningWithoutListeners()
320             throws Exception {
321         // Connect a client and disconnect it. Graph should be not running.
322         IOccupantAwarenessEventCallback first_client = registerCallbackToService();
323         mOasService.unregisterEventListener(first_client);
324 
325         assertThat(mMockHal.isGraphRunning()).isFalse();
326     }
327 
328     @Test
testUnregisterEventListener_returnsGraphRunningWithListeners()329     public void testUnregisterEventListener_returnsGraphRunningWithListeners()
330             throws Exception {
331         // Connect multipe (two) clients and disconnect one. Graph should be running.
332         IOccupantAwarenessEventCallback first_client = registerCallbackToService();
333         IOccupantAwarenessEventCallback second_client = registerCallbackToService();
334         mOasService.unregisterEventListener(first_client);
335 
336         assertThat(mMockHal.isGraphRunning()).isTrue();
337 
338         mOasService.unregisterEventListener(second_client);
339         assertThat(mMockHal.isGraphRunning()).isFalse();
340     }
341 
342     @Test
testUnregisterEventListener_returnsGraphNotRunningAfterAllListenersRemoved()343     public void testUnregisterEventListener_returnsGraphNotRunningAfterAllListenersRemoved()
344             throws Exception {
345         // Connect multipe (two) clients and disconnect them all. Graph should not be running.
346         IOccupantAwarenessEventCallback first_client = registerCallbackToService();
347         IOccupantAwarenessEventCallback second_client = registerCallbackToService();
348         mOasService.unregisterEventListener(first_client);
349         mOasService.unregisterEventListener(second_client);
350 
351         assertThat(mMockHal.isGraphRunning()).isFalse();
352     }
353 
354     /** Registers a listener to the service. */
registerCallbackToService()355     private IOccupantAwarenessEventCallback registerCallbackToService() {
356         IOccupantAwarenessEventCallback callback =
357                 new IOccupantAwarenessEventCallback.Stub() {
358                     @Override
359                     public void onStatusChanged(SystemStatusEvent systemStatusEvent) {
360                         mSystemStatus = systemStatusEvent;
361                         mStatusLatch.countDown();
362                     }
363 
364                     @Override
365                     public void onDetectionEvent(OccupantAwarenessDetection detection) {
366                     }
367                 };
368 
369         mOasService.registerEventListener(callback);
370         return callback;
371     }
372 
373     /** Resets futures for testing. */
resetStatus()374     private void resetStatus() {
375         mStatusLatch = new CountDownLatch(1);
376         mSystemStatus = null;
377     }
378 
waitStatusReady()379     private void waitStatusReady() throws Exception {
380         mStatusLatch.await(1L, TimeUnit.SECONDS);
381     }
382 
pauseForEventPropagation()383     private void pauseForEventPropagation() {
384         try {
385             mStatusLatch.await(100L, TimeUnit.MILLISECONDS);
386         } catch (Exception e) {
387         }
388     }
389 }
390