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.telemetry.publisher;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.junit.Assert.assertThrows;
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.annotation.Nullable;
35 import android.automotive.telemetry.internal.CarDataInternal;
36 import android.automotive.telemetry.internal.ICarDataListener;
37 import android.automotive.telemetry.internal.ICarTelemetryInternal;
38 import android.car.telemetry.TelemetryProto;
39 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
40 import android.os.IBinder;
41 import android.os.Looper;
42 import android.os.PersistableBundle;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.util.ArraySet;
46 
47 import com.android.car.CarLog;
48 import com.android.car.telemetry.databroker.DataSubscriber;
49 import com.android.car.telemetry.sessioncontroller.SessionAnnotation;
50 import com.android.car.telemetry.sessioncontroller.SessionController;
51 import com.android.car.test.FakeHandlerWrapper;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Captor;
58 import org.mockito.Mock;
59 import org.mockito.Mockito;
60 import org.mockito.junit.MockitoJUnitRunner;
61 
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.stream.Collectors;
66 
67 @RunWith(MockitoJUnitRunner.class)
68 public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase {
69     private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
70     private static final int CAR_DATA_ID_1 = 1;
71     private static final TelemetryProto.Publisher PUBLISHER_PARAMS_1 =
72             TelemetryProto.Publisher.newBuilder()
73                     .setCartelemetryd(TelemetryProto.CarTelemetrydPublisher.newBuilder()
74                             .setId(CAR_DATA_ID_1))
75                     .build();
76     private static final SessionAnnotation SESSION_ANNOTATION_BEGIN_1 =
77             new SessionAnnotation(1, SessionController.STATE_ENTER_DRIVING_SESSION, 0, 0, "", 0);
78     private static final String[] SESSION_ANNOTATION_KEYS =
79             {Constants.ANNOTATION_BUNDLE_KEY_SESSION_ID,
80                     Constants.ANNOTATION_BUNDLE_KEY_BOOT_REASON,
81                     Constants.ANNOTATION_BUNDLE_KEY_SESSION_STATE,
82                     Constants.ANNOTATION_BUNDLE_KEY_CREATED_AT_MILLIS,
83                     Constants.ANNOTATION_BUNDLE_KEY_CREATED_AT_SINCE_BOOT_MILLIS};
84 
85     private final FakeHandlerWrapper mFakeHandlerWrapper =
86             new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
87     private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
88 
89     @Mock
90     private IBinder mMockBinder;
91     @Mock
92     private DataSubscriber mMockDataSubscriber;
93     @Mock
94     private SessionController mMockSessionController;
95 
96     @Captor
97     private ArgumentCaptor<IBinder.DeathRecipient> mLinkToDeathCallbackCaptor;
98     @Captor
99     private ArgumentCaptor<PersistableBundle> mBundleCaptor;
100 
101     private FakeCarTelemetryInternal mFakeCarTelemetryInternal;
102     private CarTelemetrydPublisher mPublisher;
103 
CarTelemetrydPublisherTest()104     public CarTelemetrydPublisherTest() {
105         super(CarLog.TAG_TELEMETRY);
106     }
107 
buildCarDataInternal(int id, byte[] content)108     private CarDataInternal buildCarDataInternal(int id, byte[] content) {
109         CarDataInternal data = new CarDataInternal();
110         data.id = id;
111         data.content = content;
112         return data;
113     }
114 
115     @Before
setUp()116     public void setUp() throws Exception {
117         mPublisher = new CarTelemetrydPublisher(
118                 mFakePublisherListener, mFakeHandlerWrapper.getMockHandler(),
119                 mMockSessionController);
120         mFakeCarTelemetryInternal = new FakeCarTelemetryInternal(mMockBinder);
121         when(mMockDataSubscriber.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
122         when(mMockBinder.queryLocalInterface(any())).thenReturn(mFakeCarTelemetryInternal);
123         when(mMockSessionController.getSessionAnnotation()).thenReturn(SESSION_ANNOTATION_BEGIN_1);
124         doNothing().when(mMockBinder).linkToDeath(mLinkToDeathCallbackCaptor.capture(), anyInt());
125         doReturn(mMockBinder).when(() -> ServiceManager.checkService(SERVICE_NAME));
126     }
127 
128     @Override
onSessionBuilder(CustomMockitoSessionBuilder builder)129     protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
130         builder.spyStatic(ServiceManager.class);
131     }
132 
133     @Test
testAddDataSubscriber_registersNewListener()134     public void testAddDataSubscriber_registersNewListener() {
135         mPublisher.addDataSubscriber(mMockDataSubscriber);
136 
137         assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
138         assertThat(mPublisher.isConnectedToCarTelemetryd()).isTrue();
139         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
140         assertThat(mFakeCarTelemetryInternal.mCarDataIds).containsExactly(CAR_DATA_ID_1);
141     }
142 
143     @Test
testAddDataSubscriber_withInvalidId_fails()144     public void testAddDataSubscriber_withInvalidId_fails() {
145         DataSubscriber invalidDataSubscriber = Mockito.mock(DataSubscriber.class);
146         when(invalidDataSubscriber.getPublisherParam()).thenReturn(
147                 TelemetryProto.Publisher.newBuilder()
148                         .setCartelemetryd(TelemetryProto.CarTelemetrydPublisher.newBuilder()
149                                 .setId(42000))  // invalid ID
150                         .build());
151 
152         Throwable error = assertThrows(IllegalArgumentException.class,
153                 () -> mPublisher.addDataSubscriber(invalidDataSubscriber));
154 
155         assertThat(error).hasMessageThat().contains("Invalid CarData ID");
156         assertThat(mFakeCarTelemetryInternal.mListener).isNull();
157         assertThat(mPublisher.isConnectedToCarTelemetryd()).isFalse();
158         assertThat(mPublisher.hasDataSubscriber(invalidDataSubscriber)).isFalse();
159     }
160 
161     @Test
testRemoveDataSubscriber_ignoresIfNotFound()162     public void testRemoveDataSubscriber_ignoresIfNotFound() {
163         mPublisher.removeDataSubscriber(mMockDataSubscriber);
164     }
165 
166     @Test
testRemoveDataSubscriber_removesOnlySingleSubscriber()167     public void testRemoveDataSubscriber_removesOnlySingleSubscriber() {
168         DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
169         when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
170         mPublisher.addDataSubscriber(mMockDataSubscriber);
171         mPublisher.addDataSubscriber(subscriber2);
172 
173         mPublisher.removeDataSubscriber(subscriber2);
174 
175         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
176         assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
177         assertThat(mFakeCarTelemetryInternal.mListener).isNotNull();
178     }
179 
180     @Test
testRemoveDataSubscriber_disconnectsFromICarTelemetry()181     public void testRemoveDataSubscriber_disconnectsFromICarTelemetry() {
182         mPublisher.addDataSubscriber(mMockDataSubscriber);
183 
184         mPublisher.removeDataSubscriber(mMockDataSubscriber);
185 
186         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
187         assertThat(mFakeCarTelemetryInternal.mListener).isNull();
188     }
189 
190     @Test
testRemoveAllDataSubscribers_succeeds()191     public void testRemoveAllDataSubscribers_succeeds() {
192         DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
193         when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
194         mPublisher.addDataSubscriber(mMockDataSubscriber);
195         mPublisher.addDataSubscriber(subscriber2);
196 
197         mPublisher.removeAllDataSubscribers();
198 
199         assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
200         assertThat(mPublisher.hasDataSubscriber(subscriber2)).isFalse();
201         assertThat(mFakeCarTelemetryInternal.mListener).isNull();
202     }
203 
204     @Test
testNotifiesFailureConsumer_whenBinderDies()205     public void testNotifiesFailureConsumer_whenBinderDies() {
206         mPublisher.addDataSubscriber(mMockDataSubscriber);
207 
208         mLinkToDeathCallbackCaptor.getValue().binderDied();
209 
210         assertThat(mFakeCarTelemetryInternal.mSetListenerCallCount).isEqualTo(1);
211         assertThat(mFakePublisherListener.mPublisherFailure).hasMessageThat()
212                 .contains("ICarTelemetryInternal binder died");
213         assertThat(mFakePublisherListener.mFailedConfigs).hasSize(1);  // got all the failed configs
214     }
215 
216     @Test
testNotifiesFailureConsumer_whenFailsConnectToService()217     public void testNotifiesFailureConsumer_whenFailsConnectToService() {
218         mFakeCarTelemetryInternal.setApiFailure(new RemoteException("tough life"));
219 
220         mPublisher.addDataSubscriber(mMockDataSubscriber);
221 
222         assertThat(mFakePublisherListener.mPublisherFailure).hasMessageThat()
223                 .contains("Cannot set CarData listener");
224         assertThat(mFakePublisherListener.mFailedConfigs).hasSize(1);
225     }
226 
227     @Test
testPushesPublishedData_whenOnCarDataReceived()228     public void testPushesPublishedData_whenOnCarDataReceived() throws RemoteException {
229         mPublisher.addDataSubscriber(mMockDataSubscriber);
230 
231         mFakeCarTelemetryInternal.mListener.onCarDataReceived(
232                 new CarDataInternal[]{buildCarDataInternal(CAR_DATA_ID_1, new byte[]{55, 66, 77})});
233 
234         // Also verifies that the published data is not large.
235         verify(mMockDataSubscriber).push(mBundleCaptor.capture(), eq(false));
236         PersistableBundle result = mBundleCaptor.getValue();
237         // Verify published contents.
238         assertThat(result.getInt(
239                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
240                 CAR_DATA_ID_1);
241         assertThat(result.getString(
242                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo("7BM");
243         // Verify session annotations are also present.
244         assertThat(result.keySet()).containsAtLeastElementsIn(SESSION_ANNOTATION_KEYS);
245     }
246 
247     @Test
testPushesPublishedData_multipleData()248     public void testPushesPublishedData_multipleData() throws RemoteException {
249         mPublisher.addDataSubscriber(mMockDataSubscriber);
250 
251         mFakeCarTelemetryInternal.mListener.onCarDataReceived(
252                 new CarDataInternal[]{buildCarDataInternal(CAR_DATA_ID_1, new byte[]{1, 2, 3}),
253                         buildCarDataInternal(CAR_DATA_ID_1, new byte[]{3, 2, 1})});
254 
255         verify(mMockDataSubscriber, times(2)).push(mBundleCaptor.capture(),
256                 anyBoolean());
257     }
258 
259     @Test
testPushesPublishedData_multipleSubscribers()260     public void testPushesPublishedData_multipleSubscribers() throws RemoteException {
261         DataSubscriber subscriber2 = Mockito.mock(DataSubscriber.class);
262         when(subscriber2.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
263         mPublisher.addDataSubscriber(mMockDataSubscriber);
264         mPublisher.addDataSubscriber(subscriber2);
265 
266         mFakeCarTelemetryInternal.mListener.onCarDataReceived(
267                 new CarDataInternal[]{buildCarDataInternal(CAR_DATA_ID_1, new byte[]{41, 52, 63}),
268                         buildCarDataInternal(CAR_DATA_ID_1, new byte[]{53, 62, 71}),
269                         buildCarDataInternal(CAR_DATA_ID_1, new byte[]{40, 50, 60})});
270 
271         verify(mMockDataSubscriber, times(3)).push(mBundleCaptor.capture(),
272                 eq(false));
273         List<PersistableBundle> telemetryDataList = mBundleCaptor.getAllValues();
274         // Verify published contents.
275         assertThat(telemetryDataList.get(0).getInt(
276                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
277                 CAR_DATA_ID_1);
278         assertThat(telemetryDataList.get(0).getString(
279                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo(")4?");
280         // Verify session annotations are also present.
281         assertThat(telemetryDataList.get(0).keySet()).containsAtLeastElementsIn(
282                 SESSION_ANNOTATION_KEYS);
283 
284 
285         // Verify published contents.
286         assertThat(telemetryDataList.get(1).getInt(
287                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
288                 CAR_DATA_ID_1);
289         assertThat(telemetryDataList.get(1).getString(
290                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo("5>G");
291         // Verify session annotations are also present.
292         assertThat(telemetryDataList.get(1).keySet()).containsAtLeastElementsIn(
293                 SESSION_ANNOTATION_KEYS);
294 
295 
296         // Verify published contents.
297         assertThat(telemetryDataList.get(2).getInt(
298                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
299                 CAR_DATA_ID_1);
300         assertThat(telemetryDataList.get(2).getString(
301                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo("(2<");
302         // Verify session annotations are also present.
303         assertThat(telemetryDataList.get(2).keySet()).containsAtLeastElementsIn(
304                 SESSION_ANNOTATION_KEYS);
305 
306 
307         // Verify that the other subscriber received the same data.
308         verify(subscriber2, times(3)).push(mBundleCaptor.capture(), eq(false));
309         telemetryDataList = mBundleCaptor.getAllValues();
310         // Verify published contents.
311         assertThat(telemetryDataList.get(0).getInt(
312                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
313                 CAR_DATA_ID_1);
314         assertThat(telemetryDataList.get(0).getString(
315                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo(")4?");
316         // Verify session annotations are also present.
317         assertThat(telemetryDataList.get(0).keySet()).containsAtLeastElementsIn(
318                 SESSION_ANNOTATION_KEYS);
319 
320 
321         // Verify published contents.
322         assertThat(telemetryDataList.get(1).getInt(
323                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
324                 CAR_DATA_ID_1);
325         assertThat(telemetryDataList.get(1).getString(
326                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo("5>G");
327         // Verify session annotations are also present.
328         assertThat(telemetryDataList.get(1).keySet()).containsAtLeastElementsIn(
329                 SESSION_ANNOTATION_KEYS);
330 
331 
332         // Verify published contents.
333         assertThat(telemetryDataList.get(2).getInt(
334                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
335                 CAR_DATA_ID_1);
336         assertThat(telemetryDataList.get(2).getString(
337                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT)).isEqualTo("(2<");
338         // Verify session annotations are also present.
339         assertThat(telemetryDataList.get(2).keySet()).containsAtLeastElementsIn(
340                 SESSION_ANNOTATION_KEYS);
341     }
342 
343     @Test
testPushesPublishedData_noMatchingSubscribers()344     public void testPushesPublishedData_noMatchingSubscribers() throws RemoteException {
345         mPublisher.addDataSubscriber(mMockDataSubscriber);
346 
347         mFakeCarTelemetryInternal.mListener.onCarDataReceived(
348                 new CarDataInternal[]{buildCarDataInternal(10, new byte[]{1, 2, 3}),
349                         buildCarDataInternal(20, new byte[]{3, 2, 1}),
350                         buildCarDataInternal(2000, new byte[]{30, 20, 10})});
351 
352         // No subscribers are called because the generated data ids 30, 100, 2000 are not
353         // subscribed to.
354         verify(mMockDataSubscriber, never()).push(mBundleCaptor.capture(), anyBoolean());
355     }
356 
357     @Test
testPushesPublishedData_detectsLargeData()358     public void testPushesPublishedData_detectsLargeData() throws RemoteException {
359         mPublisher.addDataSubscriber(mMockDataSubscriber);
360 
361         mFakeCarTelemetryInternal.mListener.onCarDataReceived(new CarDataInternal[]{
362                 buildCarDataInternal(CAR_DATA_ID_1,
363                         new byte[DataSubscriber.SCRIPT_INPUT_SIZE_THRESHOLD_BYTES + 1])});
364 
365         // Also verifies that the published data is large.
366         verify(mMockDataSubscriber).push(mBundleCaptor.capture(), eq(true));
367         PersistableBundle result = mBundleCaptor.getValue();
368         // Verify published contents.
369         assertThat(result.getInt(
370                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_ID)).isEqualTo(
371                 CAR_DATA_ID_1);
372         assertThat(result.getString(
373                 Constants.CAR_TELEMETRYD_BUNDLE_KEY_CONTENT).length())
374                 .isEqualTo(DataSubscriber.SCRIPT_INPUT_SIZE_THRESHOLD_BYTES + 1);
375         // Verify session annotations are also present.
376         assertThat(result.keySet()).containsAtLeastElementsIn(SESSION_ANNOTATION_KEYS);
377     }
378 
379 
380     private static class FakeCarTelemetryInternal implements ICarTelemetryInternal {
381         private final IBinder mBinder;
382         @Nullable
383         ICarDataListener mListener;
384         int mSetListenerCallCount = 0;
385         @Nullable
386         private RemoteException mApiFailure = null;
387         private Set<Integer> mCarDataIds = new ArraySet<>();
388 
FakeCarTelemetryInternal(IBinder binder)389         FakeCarTelemetryInternal(IBinder binder) {
390             mBinder = binder;
391         }
392 
393         @Override
asBinder()394         public IBinder asBinder() {
395             return mBinder;
396         }
397 
398         @Override
setListener(ICarDataListener listener)399         public void setListener(ICarDataListener listener) throws RemoteException {
400             mSetListenerCallCount += 1;
401             if (mApiFailure != null) {
402                 throw mApiFailure;
403             }
404             mListener = listener;
405         }
406 
407         @Override
clearListener()408         public void clearListener() throws RemoteException {
409             if (mApiFailure != null) {
410                 throw mApiFailure;
411             }
412             mListener = null;
413         }
414 
415         @Override
addCarDataIds(int[] ids)416         public void addCarDataIds(int[] ids) throws RemoteException {
417             mCarDataIds.addAll(Arrays.stream(ids).boxed().collect(Collectors.toList()));
418         }
419 
420         @Override
removeCarDataIds(int[] ids)421         public void removeCarDataIds(int[] ids) throws RemoteException {
422             mCarDataIds.removeAll(Arrays.stream(ids).boxed().collect(Collectors.toList()));
423         }
424 
setApiFailure(RemoteException e)425         void setApiFailure(RemoteException e) {
426             mApiFailure = e;
427         }
428 
429         @Override
getInterfaceHash()430         public String getInterfaceHash() {
431             return ICarTelemetryInternal.HASH;
432         }
433 
434         @Override
getInterfaceVersion()435         public int getInterfaceVersion() {
436             return ICarTelemetryInternal.VERSION;
437         }
438     }
439 }
440