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