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