1 /*
2  * Copyright (C) 2024 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 android.car;
18 
19 import static android.car.Car.CAR_SERVICE_BINDER_SERVICE_NAME;
20 import static android.car.feature.Flags.FLAG_DISPLAY_COMPATIBILITY;
21 import static android.car.feature.Flags.FLAG_PERSIST_AP_SETTINGS;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertThrows;
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.Mockito.after;
29 import static org.mockito.Mockito.clearInvocations;
30 import static org.mockito.Mockito.doAnswer;
31 import static org.mockito.Mockito.timeout;
32 import static org.mockito.Mockito.times;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import static java.util.concurrent.TimeUnit.MILLISECONDS;
37 
38 import android.car.Car.CarBuilder;
39 import android.car.Car.CarBuilder.ServiceManager;
40 import android.car.hardware.property.CarPropertyManager;
41 import android.car.hardware.property.ICarProperty;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.ServiceConnection;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager;
47 import android.os.Build;
48 import android.os.Handler;
49 import android.os.HandlerThread;
50 import android.os.IBinder;
51 import android.os.Looper;
52 import android.os.RemoteException;
53 import android.os.SystemClock;
54 import android.platform.test.annotations.EnableFlags;
55 import android.platform.test.flag.junit.SetFlagsRule;
56 import android.platform.test.ravenwood.RavenwoodRule;
57 import android.util.Pair;
58 
59 import com.android.car.internal.ICarServiceHelper;
60 import com.android.internal.annotations.GuardedBy;
61 
62 import org.junit.After;
63 import org.junit.Before;
64 import org.junit.Rule;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.mockito.Mock;
68 import org.mockito.junit.MockitoJUnitRunner;
69 
70 import java.util.ArrayList;
71 import java.util.Collections;
72 import java.util.List;
73 import java.util.concurrent.CountDownLatch;
74 
75 /**
76  * Unit test for Car API.
77  */
78 @RunWith(MockitoJUnitRunner.Silent.class)
79 @EnableFlags({FLAG_PERSIST_AP_SETTINGS, FLAG_DISPLAY_COMPATIBILITY})
80 public final class CarUnitTest {
81 
82     private static final String TAG = CarUnitTest.class.getSimpleName();
83     private static final String PKG_NAME = "Bond.James.Bond";
84     private static final int DEFAULT_TIMEOUT_MS = 1000;
85 
86     @Rule
87     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
88     @Rule
89     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().setProvideMainThread(true)
90             .build();
91 
92     @Mock
93     private Context mContext;
94     @Mock
95     private ServiceConnection mServiceConnectionListener;
96     @Mock
97     private ComponentName mCarServiceComponentName;
98     @Mock
99     private PackageManager mPackageManager;
100     @Mock
101     private ServiceManager mServiceManager;
102     @Mock
103     private ICarProperty.Stub mICarProperty;
104     @Mock
105     private ApplicationInfo mApplicationInfo;
106 
107     private HandlerThread mEventHandlerThread;
108     private Handler mEventHandler;
109     private Handler mMainHandler;
110     private CarBuilder mCarBuilder;
111 
112     private final Object mLock = new Object();
113     @GuardedBy("mLock")
114     private final List<ServiceConnection> mBindServiceConnections = new ArrayList<>();
115     @GuardedBy("mLock")
116     private boolean mCarServiceRegistered;
117 
118     // It is tricky to mock this. So create placeholder version instead.
119     private final class FakeService extends ICar.Stub {
120 
121         @Override
setSystemServerConnections(ICarServiceHelper helper, ICarResultReceiver receiver)122         public void setSystemServerConnections(ICarServiceHelper helper,
123                 ICarResultReceiver receiver) throws RemoteException {
124         }
125 
126         @Override
isFeatureEnabled(String featureName)127         public boolean isFeatureEnabled(String featureName) {
128             return false;
129         }
130 
131         @Override
enableFeature(String featureName)132         public int enableFeature(String featureName) {
133             return Car.FEATURE_REQUEST_SUCCESS;
134         }
135 
136         @Override
disableFeature(String featureName)137         public int disableFeature(String featureName) {
138             return Car.FEATURE_REQUEST_SUCCESS;
139         }
140 
141         @Override
getAllEnabledFeatures()142         public List<String> getAllEnabledFeatures() {
143             return Collections.EMPTY_LIST;
144         }
145 
146         @Override
getAllPendingDisabledFeatures()147         public List<String> getAllPendingDisabledFeatures() {
148             return Collections.EMPTY_LIST;
149         }
150 
151         @Override
getAllPendingEnabledFeatures()152         public List<String> getAllPendingEnabledFeatures() {
153             return Collections.EMPTY_LIST;
154         }
155 
156         @Override
getCarManagerClassForFeature(String featureName)157         public String getCarManagerClassForFeature(String featureName) {
158             return null;
159         }
160 
161         @Override
getCarService(java.lang.String serviceName)162         public IBinder getCarService(java.lang.String serviceName) {
163             if (serviceName.equals(Car.PROPERTY_SERVICE)) {
164                 return mICarProperty;
165             }
166             return null;
167         }
168 
169         @Override
getCarConnectionType()170         public int getCarConnectionType() {
171             return 0;
172         }
173     };
174 
175     private final FakeService mService = new FakeService();
176 
177     private static final class LifecycleListener implements Car.CarServiceLifecycleListener {
178         private final Object mLock = new Object();
179         @GuardedBy("mLock")
180         private final ArrayList<Pair<Car, Boolean>> mEvents = new ArrayList<>();
181 
182         @Override
onLifecycleChanged(Car car, boolean ready)183         public void onLifecycleChanged(Car car, boolean ready) {
184             synchronized (mLock) {
185                 assertThat(Looper.getMainLooper()).isEqualTo(Looper.myLooper());
186                 mEvents.add(new Pair<>(car, ready));
187                 mLock.notifyAll();
188             }
189         }
190 
waitForEvent(int count, int timeoutInMs)191         void waitForEvent(int count, int timeoutInMs) throws InterruptedException {
192             synchronized (mLock) {
193                 while (mEvents.size() < count) {
194                     mLock.wait(timeoutInMs);
195                 }
196             }
197         }
198 
assertOneListenerCallAndClear(Car expectedCar, boolean ready)199         void assertOneListenerCallAndClear(Car expectedCar, boolean ready) {
200             synchronized (mLock) {
201                 assertThat(mEvents).containsExactly(new Pair<>(expectedCar, ready));
202                 mEvents.clear();
203             }
204         }
205 
assertNoEvent()206         void assertNoEvent() {
207             synchronized (mLock) {
208                 assertThat(mEvents).isEmpty();
209             }
210         }
211     }
212 
213     private final LifecycleListener mLifecycleListener = new LifecycleListener();
214 
215     @Before
setUp()216     public void setUp() {
217         mEventHandlerThread = new HandlerThread("CarTestEvent");
218         mEventHandlerThread.start();
219         mEventHandler = new Handler(mEventHandlerThread.getLooper());
220         mMainHandler = new Handler(Looper.getMainLooper());
221         // Inject mServiceManager as a dependency for creating Car.
222         mCarBuilder = new CarBuilder().setServiceManager(mServiceManager);
223 
224         when(mContext.getPackageName()).thenReturn(PKG_NAME);
225         when(mContext.getPackageManager()).thenReturn(mPackageManager);
226         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
227         setupFakeServiceManager();
228 
229         // Setup context for CarPropertyManager
230         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
231         when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
232     }
233 
234     @After
tearDown()235     public void tearDown() {
236         mEventHandlerThread.quitSafely();
237     }
238 
setupFakeServiceManager()239     private void setupFakeServiceManager() {
240         when(mContext.bindService(any(), any(), anyInt())).thenAnswer((inv) -> {
241             ServiceConnection serviceConnection = inv.getArgument(1);
242 
243             synchronized (mLock) {
244                 if (mCarServiceRegistered) {
245                     mMainHandler.post(() -> serviceConnection.onServiceConnected(
246                             mCarServiceComponentName,  mService));
247                 }
248                 mBindServiceConnections.add(serviceConnection);
249             }
250 
251             return true;
252         });
253         doAnswer((inv) -> {
254             ServiceConnection serviceConnection = inv.getArgument(0);
255 
256             synchronized (mLock) {
257                 mBindServiceConnections.remove(serviceConnection);
258             }
259             return null;
260         }).when(mContext).unbindService(any());
261 
262         when(mServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME))
263                 .thenAnswer((inv) -> {
264                     synchronized (mLock) {
265                         if (mCarServiceRegistered) {
266                             return mService;
267                         }
268                         return null;
269                     }
270                 });
271     }
272 
setCarServiceRegistered()273     private void setCarServiceRegistered() {
274         synchronized (mLock) {
275             mCarServiceRegistered = true;
276             for (int i = 0; i < mBindServiceConnections.size(); i++) {
277                 var serviceConnection = mBindServiceConnections.get(i);
278                 mMainHandler.post(() -> serviceConnection.onServiceConnected(
279                         mCarServiceComponentName, mService));
280             }
281         }
282     }
283 
setCarServiceDisconnected()284     private void setCarServiceDisconnected() {
285         synchronized (mLock) {
286             mCarServiceRegistered = false;
287             for (int i = 0; i < mBindServiceConnections.size(); i++) {
288                 var serviceConnection = mBindServiceConnections.get(i);
289                 mMainHandler.post(() -> serviceConnection.onServiceDisconnected(
290                         mCarServiceComponentName));
291             }
292         }
293     }
294 
295     @Test
testCreateCar_Context_ServiceConnection_Handler()296     public void testCreateCar_Context_ServiceConnection_Handler() {
297         Car car = Car.createCar(mContext, mServiceConnectionListener, mEventHandler);
298 
299         assertThat(car).isNotNull();
300 
301         car.connect();
302 
303         assertThat(car.isConnecting()).isTrue();
304         assertThat(car.isConnected()).isFalse();
305 
306         setCarServiceRegistered();
307 
308         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
309                 mCarServiceComponentName, mService);
310         assertThat(car.isConnected()).isTrue();
311 
312         car.disconnect();
313         assertThat(car.isConnected()).isFalse();
314     }
315 
316     @Test
testCreateCar_Context_ServiceConnection_DefaultHandler()317     public void testCreateCar_Context_ServiceConnection_DefaultHandler() {
318         Car car = Car.createCar(mContext, mServiceConnectionListener);
319 
320         assertThat(car).isNotNull();
321 
322         car.connect();
323 
324         assertThat(car.isConnecting()).isTrue();
325         assertThat(car.isConnected()).isFalse();
326 
327         setCarServiceRegistered();
328 
329         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
330                 mCarServiceComponentName, mService);
331         assertThat(car.isConnected()).isTrue();
332 
333         car.disconnect();
334         assertThat(car.isConnected()).isFalse();
335     }
336 
337     @Test
testCreateCar_Context_ServiceConnection_Handler_CarServiceRegistered()338     public void testCreateCar_Context_ServiceConnection_Handler_CarServiceRegistered() {
339         setCarServiceRegistered();
340 
341         Car car = Car.createCar(mContext, mServiceConnectionListener, mEventHandler);
342 
343         assertThat(car).isNotNull();
344 
345         car.connect();
346 
347         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
348                 mCarServiceComponentName, mService);
349         assertThat(car.isConnected()).isTrue();
350 
351         car.disconnect();
352         assertThat(car.isConnected()).isFalse();
353     }
354 
355     @Test
testCreateCar_Context_ServiceConnection_Handler_Disconnect_Reconnect()356     public void testCreateCar_Context_ServiceConnection_Handler_Disconnect_Reconnect() {
357         setCarServiceRegistered();
358 
359         Car car = Car.createCar(mContext, mServiceConnectionListener, mEventHandler);
360         car.connect();
361 
362         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
363                 mCarServiceComponentName, mService);
364         clearInvocations(mServiceConnectionListener);
365 
366         car.disconnect();
367         car.connect();
368 
369         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
370                 mCarServiceComponentName, mService);
371     }
372 
373     @Test
testCreateCar_Context_ServiceConnection_Handler_Disconnect_IgnoreCallback()374     public void testCreateCar_Context_ServiceConnection_Handler_Disconnect_IgnoreCallback() {
375         Car car = Car.createCar(mContext, mServiceConnectionListener, mEventHandler);
376         car.connect();
377         car.disconnect();
378 
379         setCarServiceRegistered();
380 
381         // Callback must not be invoked while car is disconnected.
382         verify(mServiceConnectionListener, after(DEFAULT_TIMEOUT_MS).never()).onServiceConnected(
383                 mCarServiceComponentName, mService);
384 
385         car.connect();
386 
387         // Callback should be invoked after connect again.
388         verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
389                 mCarServiceComponentName, mService);
390     }
391 
392     @Test
testCreateCar_Context_ServiceConnection_Handler_ContextIsNull()393     public void testCreateCar_Context_ServiceConnection_Handler_ContextIsNull() {
394         assertThrows(NullPointerException.class, () -> Car.createCar(
395                 /* context= */ null, mServiceConnectionListener, mEventHandler));
396     }
397 
398     @Test
testCreateCar_Context_ServiceConnection_Handler_NoAutoFeature()399     public void testCreateCar_Context_ServiceConnection_Handler_NoAutoFeature() {
400         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
401 
402         Car car = Car.createCar(mContext, mServiceConnectionListener, mEventHandler);
403 
404         assertThat(car).isNull();
405     }
406 
407     @Test
testCreateCar_Context_CarServiceRegistered()408     public void testCreateCar_Context_CarServiceRegistered() throws Exception {
409         setCarServiceRegistered();
410 
411         Car car = mCarBuilder.createCar(mContext);
412 
413         assertThat(car).isNotNull();
414         assertThat(car.isConnected()).isTrue();
415 
416         // In the legacy implementation, createCar will bind to car service and cause an
417         // onServiceConnected callback to be invoked later. We must make sure this callback is
418         // invoked before disconnect, otherwise, the callback will set isConnected to true again.
419         finishTasksOnMain();
420 
421         car.disconnect();
422         assertThat(car.isConnected()).isFalse();
423     }
424 
425     @Test
testCreateCar_Context_CarServiceRegistered_DisconnectReconnect()426     public void testCreateCar_Context_CarServiceRegistered_DisconnectReconnect() throws Exception {
427         setCarServiceRegistered();
428 
429         Car car = mCarBuilder.createCar(mContext);
430 
431         assertThat(car).isNotNull();
432         assertThat(car.isConnected()).isTrue();
433 
434         // In the legacy implementation, createCar will bind to car service and cause an
435         // onServiceConnected callback to be invoked later. We must make sure this callback is
436         // invoked before disconnect, otherwise, the callback will set isConnected to true again.
437         finishTasksOnMain();
438 
439         car.disconnect();
440         car.connect();
441 
442         // It takes a while for the callback to set connection state to connected.
443         long currentTimeMs = SystemClock.elapsedRealtime();
444         long timeout = currentTimeMs + DEFAULT_TIMEOUT_MS;
445         while (!car.isConnected() && SystemClock.elapsedRealtime() < timeout) {
446             Thread.sleep(100);
447         }
448 
449         assertThat(car.isConnected()).isTrue();
450     }
451 
452     @Test
testCreateCar_Context_CarServiceNeverRegistered_Timeout()453     public void testCreateCar_Context_CarServiceNeverRegistered_Timeout() {
454         // This should timeout.
455         Car car = mCarBuilder.createCar(mContext);
456 
457         assertThat(car).isNull();
458     }
459 
460     @Test
testCreateCar_Context_CarServiceRegisteredLater_BeforeTimeout()461     public void testCreateCar_Context_CarServiceRegisteredLater_BeforeTimeout() throws Exception {
462         // Car service is registered after 200ms.
463         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
464 
465         // This should block until car service is registered.
466         Car car = mCarBuilder.createCar(mContext);
467 
468         assertThat(car).isNotNull();
469         assertThat(car.isConnected()).isTrue();
470         verify(mContext).bindService(any(), any(), anyInt());
471 
472         // In the legacy implementation, createCar will bind to car service and cause an
473         // onServiceConnected callback to be invoked later. We must make sure this callback is
474         // invoked before disconnect, otherwise, the callback will set isConnected to true again.
475         finishTasksOnMain();
476 
477         car.disconnect();
478         assertThat(car.isConnected()).isFalse();
479     }
480 
481     @Test
testCreateCar_Context_InvokeFromMain()482     public void testCreateCar_Context_InvokeFromMain() throws Exception {
483         // Car service is registered after 200ms.
484         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
485 
486         runOnMain(() -> {
487             // This should block until car service is registered.
488             Car car = mCarBuilder.createCar(mContext);
489 
490             assertThat(car).isNotNull();
491             assertThat(car.isConnected()).isTrue();
492             verify(mContext).bindService(any(), any(), anyInt());
493 
494             car.disconnect();
495             assertThat(car.isConnected()).isFalse();
496         });
497     }
498 
499     @Test
testCreateCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater()500     public void testCreateCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater()
501             throws Exception {
502         // Car service is registered after 200ms.
503         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
504 
505         Car car = mCarBuilder.createCar(mContext, null,
506                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
507 
508         assertThat(car).isNotNull();
509         assertThat(car.isConnected()).isTrue();
510         verify(mContext).bindService(any(), any(), anyInt());
511         mLifecycleListener.assertOneListenerCallAndClear(car, true);
512 
513         // Just call these to guarantee that nothing crashes with these call.
514         ServiceConnection serviceConnection;
515         synchronized (mLock) {
516             serviceConnection = mBindServiceConnections.get(0);
517         }
518         runOnMain(() -> {
519             serviceConnection.onServiceConnected(new ComponentName("", ""), mService);
520             serviceConnection.onServiceDisconnected(new ComponentName("", ""));
521         });
522     }
523 
524     @Test
testCreateCar_Context_WaitForever_Lclistener_ConnectCrashRestart()525     public void testCreateCar_Context_WaitForever_Lclistener_ConnectCrashRestart()
526             throws Exception {
527         // Car service is registered after 100ms.
528         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 100);
529 
530         Car car = mCarBuilder.createCar(mContext, null,
531                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
532 
533         assertThat(car).isNotNull();
534         assertThat(car.isConnected()).isTrue();
535         // The callback will be called from the main thread, so it is not guaranteed to be called
536         // after createCar returns.
537         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
538         mLifecycleListener.assertOneListenerCallAndClear(car, true);
539 
540         // Fake crash.
541         mEventHandler.post(() -> setCarServiceDisconnected());
542         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
543 
544         mLifecycleListener.assertOneListenerCallAndClear(car, false);
545         assertThat(car.isConnected()).isFalse();
546 
547         // fake restart
548         mEventHandler.post(() -> setCarServiceRegistered());
549         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
550 
551         mLifecycleListener.assertOneListenerCallAndClear(car, true);
552         assertThat(car.isConnected()).isTrue();
553     }
554 
555     @Test
testCreateCar_Context_WaitForever_Lclistener_CarServiceAlreadyRegistered()556     public void testCreateCar_Context_WaitForever_Lclistener_CarServiceAlreadyRegistered()
557             throws Exception {
558         setCarServiceRegistered();
559 
560         runOnMain(() -> {
561             Car car = mCarBuilder.createCar(mContext, null,
562                     Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
563 
564             assertThat(car).isNotNull();
565             assertThat(car.isConnected()).isTrue();
566             verify(mContext, times(1)).bindService(any(), any(), anyInt());
567 
568             // mLifecycleListener should have been called as this is main thread.
569             mLifecycleListener.assertOneListenerCallAndClear(car, true);
570         });
571     }
572 
573     @Test
testCreateCar_Context_WaitForever_Lclistener_ManagerNotTheSameAfterReconnect()574     public void testCreateCar_Context_WaitForever_Lclistener_ManagerNotTheSameAfterReconnect()
575             throws Exception {
576         setCarServiceRegistered();
577 
578         Car car = mCarBuilder.createCar(mContext, null,
579                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
580 
581         CarPropertyManager oldMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
582 
583         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
584         mLifecycleListener.assertOneListenerCallAndClear(car, true);
585 
586         // Simulate car service crash.
587         setCarServiceDisconnected();
588 
589         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
590         mLifecycleListener.assertOneListenerCallAndClear(car, false);
591 
592         // Simulate car service restore.
593         setCarServiceRegistered();
594 
595         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
596         mLifecycleListener.assertOneListenerCallAndClear(car, true);
597         CarPropertyManager newMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
598 
599         assertThat(oldMgr).isNotEqualTo(newMgr);
600     }
601 
602     @Test
testCreateCar_Context_DoNotWait_CarServiceRegistered()603     public void testCreateCar_Context_DoNotWait_CarServiceRegistered()
604             throws Exception {
605         setCarServiceRegistered();
606 
607         Car car = mCarBuilder.createCar(mContext, null,
608                 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
609 
610         assertThat(car).isNotNull();
611         assertThat(car.isConnected()).isTrue();
612         verify(mContext).bindService(any(), any(), anyInt());
613 
614         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
615         mLifecycleListener.assertOneListenerCallAndClear(car, true);
616     }
617 
618     @Test
testCreateCar_Context_DoNotWait_CarServiceCrash_Restore()619     public void testCreateCar_Context_DoNotWait_CarServiceCrash_Restore()
620             throws Exception {
621         setCarServiceRegistered();
622 
623         Car car = mCarBuilder.createCar(mContext, null,
624                 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
625 
626         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
627         mLifecycleListener.assertOneListenerCallAndClear(car, true);
628 
629         // Simulate car service crash.
630         setCarServiceDisconnected();
631 
632         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
633         mLifecycleListener.assertOneListenerCallAndClear(car, false);
634         assertThat(car.isConnected()).isFalse();
635 
636         // Simulate car service restore.
637         setCarServiceRegistered();
638 
639         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
640         mLifecycleListener.assertOneListenerCallAndClear(car, true);
641         assertThat(car.isConnected()).isTrue();
642     }
643 
644     @Test
testCreateCar_Context_DoNotWait_InvokeFromMain_CarServiceRegistered()645     public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarServiceRegistered()
646             throws Exception {
647         setCarServiceRegistered();
648 
649         runOnMain(() -> {
650             Car car = mCarBuilder.createCar(mContext, null,
651                     Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
652 
653             assertThat(car).isNotNull();
654             assertThat(car.isConnected()).isTrue();
655             verify(mContext).bindService(any(), any(), anyInt());
656             // createCar is called from main handler, so callback must have already been called.
657             mLifecycleListener.assertOneListenerCallAndClear(car, true);
658         });
659     }
660 
661     @Test
testCreateCar_Context_DoNotWait_CarServiceRegisteredLater()662     public void testCreateCar_Context_DoNotWait_CarServiceRegisteredLater()
663             throws Exception {
664         Car car = mCarBuilder.createCar(mContext, null,
665                 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
666 
667         assertThat(car).isNotNull();
668         assertThat(car.isConnected()).isFalse();
669         verify(mContext).bindService(any(), any(), anyInt());
670 
671         setCarServiceRegistered();
672 
673         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
674         mLifecycleListener.assertOneListenerCallAndClear(car, true);
675     }
676 
677     @Test
testCreateCar_Context_DoNotWait_CarServiceRegisteredAfterDisconnect()678     public void testCreateCar_Context_DoNotWait_CarServiceRegisteredAfterDisconnect()
679             throws Exception {
680         Car car = mCarBuilder.createCar(mContext, null,
681                 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
682 
683         assertThat(car).isNotNull();
684         assertThat(car.isConnected()).isFalse();
685         verify(mContext).bindService(any(), any(), anyInt());
686 
687         car.disconnect();
688 
689         // Car service is registered after disconnect, must not invoke callback.
690         setCarServiceRegistered();
691 
692         Thread.sleep(DEFAULT_TIMEOUT_MS);
693         mLifecycleListener.assertNoEvent();
694 
695         // After connect, the callback must be invoked.
696         car.connect();
697 
698         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
699         mLifecycleListener.assertOneListenerCallAndClear(car, true);
700     }
701 
702     @Test
testCreateCar_Context_DoNotWait_InvokeFromMain_CarServiceRegisteredLater()703     public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarServiceRegisteredLater()
704             throws Exception {
705         setCarServiceRegistered();
706 
707         runOnMain(() -> {
708             Car car = mCarBuilder.createCar(mContext, null,
709                     Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
710 
711             assertThat(car).isNotNull();
712             assertThat(car.isConnected()).isTrue();
713             verify(mContext).bindService(any(), any(), anyInt());
714             // createCar is called from main handler, so callback must have already been called.
715             mLifecycleListener.assertOneListenerCallAndClear(car, true);
716         });
717     }
718 
719     @Test
testCreateCar_Context_WithTimeout_InvokeFromMain_CarServiceRegisteredLater()720     public void testCreateCar_Context_WithTimeout_InvokeFromMain_CarServiceRegisteredLater()
721             throws Exception {
722         // Car service is registered after 200ms.
723         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
724 
725         runOnMain(() -> {
726             Car car = mCarBuilder.createCar(mContext, null, DEFAULT_TIMEOUT_MS, mLifecycleListener);
727 
728             assertThat(car).isNotNull();
729             assertThat(car.isConnected()).isTrue();
730             verify(mContext).bindService(any(), any(), anyInt());
731             // createCar is called from main handler, so callback must have already been called.
732             mLifecycleListener.assertOneListenerCallAndClear(car, true);
733         });
734     }
735 
736     @Test
testCreateCar_Context_WithTimeout_CarServiceRegisteredAfterTimeout()737     public void testCreateCar_Context_WithTimeout_CarServiceRegisteredAfterTimeout()
738             throws Exception {
739         // Car service is registered after 200ms.
740         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
741 
742         Car car = mCarBuilder.createCar(mContext, null, 50, mLifecycleListener);
743         assertThat(car).isNotNull();
744         assertThat(car.isConnected()).isFalse();
745         verify(mContext).bindService(any(), any(), anyInt());
746 
747         // The callback should be invoked after 200ms.
748         mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
749         mLifecycleListener.assertOneListenerCallAndClear(car, true);
750         assertThat(car.isConnected()).isTrue();
751     }
752 
753     @Test
testCreateCar_Context_WaitForever_InvokeFromMain_CarServiceRegisteredLater()754     public void testCreateCar_Context_WaitForever_InvokeFromMain_CarServiceRegisteredLater()
755             throws Exception {
756         // Car service is registered after 200ms.
757         mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
758 
759         runOnMain(() -> {
760             Car car = mCarBuilder.createCar(mContext, null,
761                     Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
762 
763             assertThat(car).isNotNull();
764             assertThat(car.isConnected()).isTrue();
765             verify(mContext, times(1)).bindService(any(), any(), anyInt());
766 
767             // mLifecycleListener should have been called as this is main thread.
768             mLifecycleListener.assertOneListenerCallAndClear(car, true);
769         });
770     }
771 
runOnMain(Runnable runnable)772     private void runOnMain(Runnable runnable) throws InterruptedException {
773         var cdLatch = new CountDownLatch(1);
774         mMainHandler.post(() -> {
775             runnable.run();
776             cdLatch.countDown();
777         });
778         cdLatch.await(DEFAULT_TIMEOUT_MS, MILLISECONDS);
779     }
780 
finishTasksOnMain()781     private void finishTasksOnMain() throws InterruptedException {
782         // Do nothing on main just to make sure main finished handling the callbacks.
783         runOnMain(() -> {});
784     }
785 }
786