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