1 /* 2 * Copyright (C) 2015 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.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 19 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.fail; 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.Mockito.doAnswer; 24 import static org.mockito.Mockito.mock; 25 26 import android.car.Car; 27 import android.car.test.CarTestManager; 28 import android.car.test.CarTestManagerBinderWrapper; 29 import android.car.user.CarUserManager.UserLifecycleListener; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.ContextWrapper; 33 import android.content.Intent; 34 import android.content.ServiceConnection; 35 import android.content.res.Resources; 36 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 37 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; 38 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode; 39 import android.os.Binder; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.UserHandle; 44 import android.util.Log; 45 import android.util.SparseArray; 46 47 import androidx.test.annotation.UiThreadTest; 48 import androidx.test.platform.app.InstrumentationRegistry; 49 50 import com.android.car.pm.CarPackageManagerService; 51 import com.android.car.systeminterface.ActivityManagerInterface; 52 import com.android.car.systeminterface.DisplayInterface; 53 import com.android.car.systeminterface.IOInterface; 54 import com.android.car.systeminterface.StorageMonitoringInterface; 55 import com.android.car.systeminterface.SystemInterface; 56 import com.android.car.systeminterface.SystemInterface.Builder; 57 import com.android.car.systeminterface.SystemStateInterface; 58 import com.android.car.systeminterface.TimeInterface; 59 import com.android.car.systeminterface.WakeLockInterface; 60 import com.android.car.test.utils.TemporaryDirectory; 61 import com.android.car.user.CarUserService; 62 import com.android.car.vehiclehal.test.MockedVehicleHal; 63 import com.android.car.vehiclehal.test.MockedVehicleHal.DefaultPropertyHandler; 64 import com.android.car.vehiclehal.test.MockedVehicleHal.StaticPropertyHandler; 65 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; 66 import com.android.car.vehiclehal.test.VehiclePropConfigBuilder; 67 import com.android.car.watchdog.CarWatchdogService; 68 69 import org.junit.After; 70 import org.junit.Before; 71 import org.mockito.MockitoSession; 72 import org.mockito.quality.Strictness; 73 74 import java.io.File; 75 import java.io.IOException; 76 import java.time.Duration; 77 import java.util.ArrayList; 78 import java.util.HashMap; 79 import java.util.List; 80 import java.util.Map; 81 82 /** 83 * Base class for testing with mocked vehicle HAL (=car). 84 * It is up to each app to start emulation by getMockedVehicleHal().start() as there will be 85 * per test set up that should be done before starting. 86 */ 87 public class MockedCarTestBase { 88 static final long DEFAULT_WAIT_TIMEOUT_MS = 3000; 89 static final long SHORT_WAIT_TIMEOUT_MS = 500; 90 private static final String TAG = MockedCarTestBase.class.getSimpleName(); 91 private static final IBinder sCarServiceToken = new Binder(); 92 private static boolean sRealCarServiceReleased; 93 94 private Car mCar; 95 private ICarImpl mCarImpl; 96 private MockedVehicleHal mMockedVehicleHal; 97 private SystemInterface mFakeSystemInterface; 98 private MockResources mResources; 99 private MockedCarTestContext mMockedCarTestContext; 100 101 private final List<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>(); 102 private final CarUserService mCarUserService = mock(CarUserService.class); 103 private final MockIOInterface mMockIOInterface = new MockIOInterface(); 104 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 105 private final Map<VehiclePropConfigBuilder, VehicleHalPropertyHandler> mHalConfig = 106 new HashMap<>(); 107 private final SparseArray<VehiclePropConfigBuilder> mPropToConfigBuilder = new SparseArray<>(); 108 private final CarWatchdogService mCarWatchdogService = mock(CarWatchdogService.class); 109 110 private MockitoSession mSession; 111 createMockedVehicleHal()112 protected synchronized MockedVehicleHal createMockedVehicleHal() { 113 return new MockedVehicleHal(); 114 } 115 getMockedVehicleHal()116 protected synchronized MockedVehicleHal getMockedVehicleHal() { 117 return mMockedVehicleHal; 118 } 119 getFakeSystemInterface()120 protected synchronized SystemInterface getFakeSystemInterface() { 121 return mFakeSystemInterface; 122 } 123 configureMockedHal()124 protected synchronized void configureMockedHal() { 125 } 126 spyOnBeforeCarImplInit()127 protected synchronized void spyOnBeforeCarImplInit() { 128 } 129 getSystemInterfaceBuilder()130 protected synchronized SystemInterface.Builder getSystemInterfaceBuilder() { 131 return Builder.newSystemInterface() 132 .withSystemStateInterface(new MockSystemStateInterface()) 133 .withActivityManagerInterface(new MockActivityManagerInterface()) 134 .withDisplayInterface(new MockDisplayInterface()) 135 .withIOInterface(mMockIOInterface) 136 .withStorageMonitoringInterface(new MockStorageMonitoringInterface()) 137 .withTimeInterface(new MockTimeInterface()) 138 .withWakeLockInterface(new MockWakeLockInterface()); 139 } 140 configureFakeSystemInterface()141 protected synchronized void configureFakeSystemInterface() {} 142 configureResourceOverrides(MockResources resources)143 protected synchronized void configureResourceOverrides(MockResources resources) { 144 resources.overrideResource(com.android.car.R.string.instrumentClusterRendererService, ""); 145 resources.overrideResource(com.android.car.R.bool.audioUseDynamicRouting, false); 146 resources.overrideResource(com.android.car.R.array.config_earlyStartupServices, 147 new String[0]); 148 } 149 getContext()150 protected synchronized Context getContext() { 151 if (mMockedCarTestContext == null) { 152 mMockedCarTestContext = createMockedCarTestContext( 153 InstrumentationRegistry.getInstrumentation().getTargetContext()); 154 } 155 return mMockedCarTestContext; 156 } 157 createMockedCarTestContext(Context context)158 protected MockedCarTestContext createMockedCarTestContext(Context context) { 159 return new MockedCarTestContext(context); 160 } 161 getTestContext()162 protected Context getTestContext() { 163 return InstrumentationRegistry.getInstrumentation().getContext(); 164 } 165 getFlattenComponent(Class cls)166 protected String getFlattenComponent(Class cls) { 167 ComponentName cn = new ComponentName(getTestContext(), cls); 168 return cn.flattenToString(); 169 } 170 171 /** Child class should override this to configure mocking in different way */ createMockingSession()172 protected MockitoSession createMockingSession() { 173 return mockitoSession() 174 .initMocks(this) 175 .strictness(Strictness.LENIENT) 176 .startMocking(); 177 } 178 179 @Before 180 @UiThreadTest setUp()181 public void setUp() throws Exception { 182 Log.i(TAG, "setUp"); 183 184 mSession = createMockingSession(); 185 186 releaseRealCarService(getContext()); 187 188 mMockedVehicleHal = createMockedVehicleHal(); 189 configureMockedHal(); 190 191 mFakeSystemInterface = getSystemInterfaceBuilder().build(); 192 configureFakeSystemInterface(); 193 194 mMockedCarTestContext = (MockedCarTestContext) getContext(); 195 configureResourceOverrides((MockResources) mMockedCarTestContext.getResources()); 196 197 doAnswer((invocation) -> { 198 UserLifecycleListener listener = invocation.getArgument(0); 199 Log.d(TAG, "Adding UserLifecycleListener: " + listener); 200 mUserLifecycleListeners.add(listener); 201 return null; 202 }).when(mCarUserService).addUserLifecycleListener(any()); 203 204 doAnswer((invocation) -> { 205 UserLifecycleListener listener = invocation.getArgument(0); 206 Log.d(TAG, "Removing UserLifecycleListener: " + listener); 207 mUserLifecycleListeners.remove(listener); 208 return null; 209 }).when(mCarUserService).removeUserLifecycleListener(any()); 210 211 // ICarImpl will register new CarLocalServices services. 212 // This prevents one test failure in tearDown from triggering assertion failure for single 213 // CarLocalServices service. 214 CarLocalServices.removeAllServices(); 215 216 // This should be done here as feature property is accessed inside the constructor. 217 initMockedHal(); 218 mCarImpl = new ICarImpl(mMockedCarTestContext, mMockedVehicleHal, mFakeSystemInterface, 219 /* errorNotifier= */ null , "MockedCar", mCarUserService, mCarWatchdogService); 220 221 spyOnBeforeCarImplInit(); 222 mCarImpl.init(); 223 mCar = new Car(mMockedCarTestContext, mCarImpl, null /* handler */); 224 } 225 226 @After 227 @UiThreadTest tearDown()228 public void tearDown() throws Exception { 229 Log.i(TAG, "tearDown"); 230 231 try { 232 if (mCar != null) { 233 mCar.disconnect(); 234 mCar = null; 235 } 236 if (mCarImpl != null) { 237 mCarImpl.release(); 238 mCarImpl = null; 239 } 240 CarServiceUtils.finishAllHandlerTasks(); 241 if (mMockIOInterface != null) { 242 mMockIOInterface.tearDown(); 243 } 244 mMockedVehicleHal = null; 245 } finally { 246 if (mSession != null) { 247 mSession.finishMocking(); 248 } 249 } 250 } 251 getCarPropertyService()252 public CarPropertyService getCarPropertyService() { 253 return (CarPropertyService) mCarImpl.getCarService(Car.PROPERTY_SERVICE); 254 } 255 injectErrorEvent(int propId, int areaId, int errorCode)256 public void injectErrorEvent(int propId, int areaId, int errorCode) { 257 mMockedVehicleHal.injectError(errorCode, propId, areaId); 258 } 259 260 /** 261 * Creates new Car instance for testing. 262 */ createNewCar()263 public Car createNewCar() { 264 return new Car(mMockedCarTestContext, mCarImpl, null /* handler */); 265 } 266 getPackageManagerService()267 public CarPackageManagerService getPackageManagerService() { 268 return (CarPackageManagerService) mCarImpl.getCarService(Car.PACKAGE_SERVICE); 269 } 270 reinitializeMockedHal()271 protected synchronized void reinitializeMockedHal() throws Exception { 272 mCarImpl.release(); 273 initMockedHal(); 274 } 275 initMockedHal()276 private synchronized void initMockedHal() throws Exception { 277 for (Map.Entry<VehiclePropConfigBuilder, VehicleHalPropertyHandler> entry 278 : mHalConfig.entrySet()) { 279 mMockedVehicleHal.addProperty(entry.getKey().build(), entry.getValue()); 280 } 281 mHalConfig.clear(); 282 } 283 addProperty(int propertyId, VehicleHalPropertyHandler propertyHandler)284 protected synchronized VehiclePropConfigBuilder addProperty(int propertyId, 285 VehicleHalPropertyHandler propertyHandler) { 286 VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId); 287 setConfigBuilder(builder, propertyHandler); 288 return builder; 289 } 290 addProperty(int propertyId)291 protected synchronized VehiclePropConfigBuilder addProperty(int propertyId) { 292 VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId); 293 setConfigBuilder(builder, new DefaultPropertyHandler(builder.build(), null)); 294 return builder; 295 } 296 addProperty(int propertyId, VehiclePropValue value)297 protected synchronized VehiclePropConfigBuilder addProperty(int propertyId, 298 VehiclePropValue value) { 299 VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId); 300 setConfigBuilder(builder, new DefaultPropertyHandler(builder.build(), value)); 301 return builder; 302 } 303 addStaticProperty(int propertyId, VehiclePropValue value)304 protected synchronized VehiclePropConfigBuilder addStaticProperty(int propertyId, 305 VehiclePropValue value) { 306 VehiclePropConfigBuilder builder = VehiclePropConfigBuilder.newBuilder(propertyId) 307 .setChangeMode(VehiclePropertyChangeMode.STATIC) 308 .setAccess(VehiclePropertyAccess.READ); 309 310 setConfigBuilder(builder, new StaticPropertyHandler(value)); 311 return builder; 312 } 313 setConfigBuilder(VehiclePropConfigBuilder builder, VehicleHalPropertyHandler propertyHandler)314 private void setConfigBuilder(VehiclePropConfigBuilder builder, 315 VehicleHalPropertyHandler propertyHandler) { 316 int propId = builder.build().prop; 317 318 // Override previous property config if exists. 319 VehiclePropConfigBuilder prevBuilder = mPropToConfigBuilder.get(propId); 320 if (prevBuilder != null) { 321 mHalConfig.remove(prevBuilder); 322 } 323 mPropToConfigBuilder.put(propId, builder); 324 mHalConfig.put(builder, propertyHandler); 325 } 326 getCar()327 protected synchronized android.car.Car getCar() { 328 return mCar; 329 } 330 331 /* 332 * In order to eliminate interfering with real car service we will disable it. It will be 333 * enabled back in CarTestService when sCarServiceToken will go away (tests finish). 334 */ releaseRealCarService(Context context)335 private synchronized static void releaseRealCarService(Context context) throws Exception { 336 if (sRealCarServiceReleased) { 337 return; // We just want to release it once. 338 } 339 sRealCarServiceReleased = true; // To make sure it was called once. 340 341 Object waitForConnection = new Object(); 342 Car car = android.car.Car.createCar(context, new ServiceConnection() { 343 @Override 344 public void onServiceConnected(ComponentName name, IBinder service) { 345 synchronized (waitForConnection) { 346 waitForConnection.notify(); 347 } 348 } 349 350 @Override 351 public void onServiceDisconnected(ComponentName name) { } 352 }); 353 354 car.connect(); 355 synchronized (waitForConnection) { 356 if (!car.isConnected()) { 357 waitForConnection.wait(DEFAULT_WAIT_TIMEOUT_MS); 358 } 359 } 360 361 if (car.isConnected()) { 362 Log.i(TAG, "Connected to real car service"); 363 CarTestManagerBinderWrapper binderWrapper = 364 (CarTestManagerBinderWrapper) car.getCarManager(Car.TEST_SERVICE); 365 assertNotNull(binderWrapper); 366 367 CarTestManager mgr = new CarTestManager(car, binderWrapper.binder); 368 mgr.stopCarService(sCarServiceToken); 369 } 370 } 371 372 static final class MockActivityManagerInterface implements ActivityManagerInterface { 373 @Override sendBroadcastAsUser(Intent intent, UserHandle user)374 public void sendBroadcastAsUser(Intent intent, UserHandle user) { 375 Log.d(TAG, "Broadcast intent: " + intent.getAction() + " as user: " + user); 376 } 377 } 378 379 static final class MockDisplayInterface implements DisplayInterface { 380 381 @Override setDisplayBrightness(int brightness)382 public void setDisplayBrightness(int brightness) {} 383 384 @Override setDisplayState(boolean on)385 public void setDisplayState(boolean on) {} 386 387 @Override startDisplayStateMonitoring(CarPowerManagementService service)388 public void startDisplayStateMonitoring(CarPowerManagementService service) {} 389 390 @Override stopDisplayStateMonitoring()391 public void stopDisplayStateMonitoring() {} 392 393 @Override refreshDisplayBrightness()394 public void refreshDisplayBrightness() {} 395 } 396 397 static final class MockIOInterface implements IOInterface { 398 private TemporaryDirectory mFilesDir = null; 399 400 @Override getSystemCarDir()401 public File getSystemCarDir() { 402 if (mFilesDir == null) { 403 try { 404 mFilesDir = new TemporaryDirectory(TAG); 405 } catch (IOException e) { 406 Log.e(TAG, "failed to create temporary directory", e); 407 fail("failed to create temporary directory. exception was: " + e); 408 } 409 } 410 return mFilesDir.getDirectory(); 411 } 412 tearDown()413 public void tearDown() { 414 if (mFilesDir != null) { 415 try { 416 mFilesDir.close(); 417 } catch (Exception e) { 418 Log.w(TAG, "could not remove temporary directory", e); 419 } 420 } 421 } 422 } 423 424 /** 425 * Special version of {@link ContextWrapper} that overrides {@method getResources} by returning 426 * a {@link MockResources}, so tests are free to set resources. This class represents an 427 * alternative of using Mockito spy (see b/148240178). 428 * 429 * Tests may specialize this class. If they decide so, then they are required to override 430 * {@method newMockedCarContext} to provide their own context. 431 */ 432 protected static class MockedCarTestContext extends ContextWrapper { 433 434 private final Resources mMockedResources; 435 MockedCarTestContext(Context base)436 MockedCarTestContext(Context base) { 437 super(base); 438 mMockedResources = new MockResources(base.getResources()); 439 } 440 441 @Override getResources()442 public Resources getResources() { 443 return mMockedResources; 444 } 445 } 446 447 static final class MockResources extends Resources { 448 private final HashMap<Integer, Boolean> mBooleanOverrides = new HashMap<>(); 449 private final HashMap<Integer, Integer> mIntegerOverrides = new HashMap<>(); 450 private final HashMap<Integer, String> mStringOverrides = new HashMap<>(); 451 private final HashMap<Integer, String[]> mStringArrayOverrides = new HashMap<>(); 452 MockResources(Resources resources)453 MockResources(Resources resources) { 454 super(resources.getAssets(), 455 resources.getDisplayMetrics(), 456 resources.getConfiguration()); 457 } 458 459 @Override getBoolean(int id)460 public boolean getBoolean(int id) { 461 return mBooleanOverrides.getOrDefault(id, 462 super.getBoolean(id)); 463 } 464 465 @Override getInteger(int id)466 public int getInteger(int id) { 467 return mIntegerOverrides.getOrDefault(id, 468 super.getInteger(id)); 469 } 470 471 @Override getString(int id)472 public String getString(int id) { 473 return mStringOverrides.getOrDefault(id, 474 super.getString(id)); 475 } 476 477 @Override getStringArray(int id)478 public String[] getStringArray(int id) { 479 return mStringArrayOverrides.getOrDefault(id, 480 super.getStringArray(id)); 481 } 482 overrideResource(int id, boolean value)483 MockResources overrideResource(int id, boolean value) { 484 mBooleanOverrides.put(id, value); 485 return this; 486 } 487 overrideResource(int id, int value)488 MockResources overrideResource(int id, int value) { 489 mIntegerOverrides.put(id, value); 490 return this; 491 } 492 overrideResource(int id, String value)493 MockResources overrideResource(int id, String value) { 494 mStringOverrides.put(id, value); 495 return this; 496 } 497 overrideResource(int id, String[] value)498 MockResources overrideResource(int id, String[] value) { 499 mStringArrayOverrides.put(id, value); 500 return this; 501 } 502 } 503 504 static final class MockStorageMonitoringInterface implements StorageMonitoringInterface {} 505 506 static final class MockSystemStateInterface implements SystemStateInterface { 507 @Override shutdown()508 public void shutdown() {} 509 510 @Override enterDeepSleep()511 public boolean enterDeepSleep() { 512 return true; 513 } 514 515 @Override scheduleActionForBootCompleted(Runnable action, Duration delay)516 public void scheduleActionForBootCompleted(Runnable action, Duration delay) {} 517 } 518 519 static final class MockTimeInterface implements TimeInterface { 520 521 @Override scheduleAction(Runnable r, long delayMs)522 public void scheduleAction(Runnable r, long delayMs) {} 523 524 @Override cancelAllActions()525 public void cancelAllActions() {} 526 } 527 528 static final class MockWakeLockInterface implements WakeLockInterface { 529 530 @Override releaseAllWakeLocks()531 public void releaseAllWakeLocks() {} 532 533 @Override switchToPartialWakeLock()534 public void switchToPartialWakeLock() {} 535 536 @Override switchToFullWakeLock()537 public void switchToFullWakeLock() {} 538 } 539 } 540