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.remoteaccess;
18 
19 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_CUSTOM;
20 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_ENTER_GARAGE_MODE;
21 import static android.car.remoteaccess.ICarRemoteAccessService.SERVICE_ERROR_CODE_GENERAL;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assert.assertThrows;
27 import static org.mockito.Mockito.any;
28 import static org.mockito.Mockito.anyString;
29 import static org.mockito.Mockito.doThrow;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.timeout;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import android.car.feature.FakeFeatureFlagsImpl;
37 import android.car.feature.Flags;
38 import android.car.remoteaccess.CarRemoteAccessManager.CompletableRemoteTaskFuture;
39 import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskSchedulerException;
40 import android.car.remoteaccess.CarRemoteAccessManager.RemoteTaskClientCallback;
41 import android.car.remoteaccess.CarRemoteAccessManager.ScheduleInfo;
42 import android.car.remoteaccess.CarRemoteAccessManager.TaskType;
43 import android.car.test.AbstractExpectableTestCase;
44 import android.car.test.mocks.JavaMockitoHelper;
45 import android.os.IBinder;
46 import android.os.RemoteException;
47 import android.os.ServiceSpecificException;
48 import android.platform.test.ravenwood.RavenwoodRule;
49 
50 import com.android.car.internal.ICarBase;
51 
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Captor;
58 import org.mockito.Mock;
59 import org.mockito.junit.MockitoJUnitRunner;
60 
61 import java.time.Duration;
62 import java.util.List;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.Executor;
65 
66 /**
67  * <p>This class contains unit tests for the {@link CarRemoteAccessManager}.
68  */
69 @RunWith(MockitoJUnitRunner.class)
70 public final class CarRemoteAccessManagerUnitTest extends AbstractExpectableTestCase {
71     private static final int DEFAULT_TIMEOUT = 3000;
72 
73     private static final String TEST_SCHEDULE_ID = "test schedule id";
74     private static final byte[] TEST_TASK_DATA = new byte[]{
75             (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef};
76     private static final @TaskType int TEST_TASK_TYPE = TASK_TYPE_CUSTOM;
77     private static final long TEST_START_TIME = 1234;
78     private static final int TEST_TASK_COUNT = 10;
79     private static final Duration TEST_PERIODIC = Duration.ofSeconds(1);
80 
81     @Rule
82     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().setProcessApp().build();
83 
84     @Mock
85     private ICarBase mCar;
86     @Mock
87     private IBinder mBinder;
88     @Mock
89     private ICarRemoteAccessService mService;
90     @Captor
91     private ArgumentCaptor<CompletableRemoteTaskFuture> mFutureCaptor;
92 
93     private CarRemoteAccessManager mRemoteAccessManager;
94     private final Executor mExecutor = Runnable::run;
95 
96     @Before
setUp()97     public void setUp() throws Exception {
98         when(mBinder.queryLocalInterface(anyString())).thenReturn(mService);
99         when(mCar.handleRemoteExceptionFromCarService(any(RemoteException.class), any()))
100                 .thenAnswer((inv) -> {
101                     return inv.getArgument(1);
102                 });
103         mRemoteAccessManager = new CarRemoteAccessManager(mCar, mBinder);
104     }
105 
106     @Test
testSetRemoteTaskClient()107     public void testSetRemoteTaskClient() throws Exception {
108         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
109 
110         mRemoteAccessManager.setRemoteTaskClient(mExecutor, remoteTaskClient);
111 
112         verify(mService).addCarRemoteTaskClient(any(ICarRemoteAccessCallback.class));
113     }
114 
115     @Test
testSetRemoteTaskClient_invalidArguments()116     public void testSetRemoteTaskClient_invalidArguments() throws Exception {
117         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
118 
119         assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.setRemoteTaskClient(
120                 /* executor= */ null, remoteTaskClient));
121         assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.setRemoteTaskClient(
122                 mExecutor, /* callback= */ null));
123     }
124 
125     @Test
testSetRemoteTaskClient_doubleRegistration()126     public void testSetRemoteTaskClient_doubleRegistration() throws Exception {
127         RemoteTaskClient remoteTaskClientOne = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
128         RemoteTaskClient remoteTaskClientTwo = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
129 
130         mRemoteAccessManager.setRemoteTaskClient(mExecutor, remoteTaskClientOne);
131 
132         assertThrows(IllegalStateException.class, () -> mRemoteAccessManager.setRemoteTaskClient(
133                 mExecutor, remoteTaskClientTwo));
134     }
135 
136     @Test
testSetRmoteTaskClient_remoteException()137     public void testSetRmoteTaskClient_remoteException() throws Exception {
138         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
139         doThrow(RemoteException.class).when(mService)
140                 .addCarRemoteTaskClient(any(ICarRemoteAccessCallback.class));
141 
142         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
143         internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing",
144                 /* data= */ null, /* taskMaxDurationInSec= */ 10);
145 
146         assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull();
147     }
148 
149     @Test
testClearRemoteTaskClient()150     public void testClearRemoteTaskClient() throws Exception {
151         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
152         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
153 
154         mRemoteAccessManager.clearRemoteTaskClient();
155         internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing",
156                 /* data= */ null, /* taskMaxDurationInSec= */ 10);
157 
158         assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull();
159     }
160 
161     @Test
testClearRemoteTaskClient_remoteException()162     public void testClearRemoteTaskClient_remoteException() throws Exception {
163         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0);
164         doThrow(RemoteException.class).when(mService)
165                 .removeCarRemoteTaskClient(any(ICarRemoteAccessCallback.class));
166         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
167 
168         mRemoteAccessManager.clearRemoteTaskClient();
169         internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing",
170                 /* data= */ null, /* taskMaxDurationInSec= */ 10);
171 
172         assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull();
173     }
174 
175     @Test
testClientRegistration()176     public void testClientRegistration() throws Exception {
177         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1);
178         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
179         String serviceId = "serviceId_testing";
180         String vehicleId = "vehicleId_testing";
181         String processorId = "processorId_testing";
182         String clientId = "clientId_testing";
183 
184         internalCallback.onClientRegistrationUpdated(
185                 new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId));
186 
187         assertWithMessage("Service ID").that(remoteTaskClient.getServiceId()).isEqualTo(serviceId);
188         assertWithMessage("Vehicle ID").that(remoteTaskClient.getVehicleId()).isEqualTo(vehicleId);
189         assertWithMessage("Processor ID").that(remoteTaskClient.getProcessorId())
190                 .isEqualTo(processorId);
191         assertWithMessage("Client ID").that(remoteTaskClient.getClientId()).isEqualTo(clientId);
192     }
193 
194     @Test
testServerlessClientRegistration()195     public void testServerlessClientRegistration() throws Exception {
196         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1);
197         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
198         String clientId = "clientId_testing";
199         FakeFeatureFlagsImpl fakeFlagsImpl = new FakeFeatureFlagsImpl();
200         fakeFlagsImpl.setFlag(Flags.FLAG_SERVERLESS_REMOTE_ACCESS, true);
201         mRemoteAccessManager.setFeatureFlags(fakeFlagsImpl);
202 
203         internalCallback.onServerlessClientRegistered(clientId);
204 
205         assertThat(remoteTaskClient.isServerlessClientRegistered()).isTrue();
206     }
207 
208     @Test
testClientRegistrationFail()209     public void testClientRegistrationFail() throws Exception {
210         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1);
211         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
212 
213         internalCallback.onClientRegistrationFailed();
214 
215         assertWithMessage("Registration fail").that(remoteTaskClient.isRegistrationFail()).isTrue();
216     }
217 
218     @Test
testRemoteTaskRequested()219     public void testRemoteTaskRequested() throws Exception {
220         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2);
221         String clientId = "clientId_testing";
222         String taskId = "taskId_testing";
223         prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null);
224 
225         assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isEqualTo(taskId);
226         assertWithMessage("Data").that(remoteTaskClient.getData()).isNull();
227     }
228 
229     @Test
testRemoteTaskRequested_withData()230     public void testRemoteTaskRequested_withData() throws Exception {
231         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2);
232         String clientId = "clientId_testing";
233         String taskId = "taskId_testing";
234         byte[] data = new byte[]{1, 2, 3, 4};
235         prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, data);
236 
237         assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isEqualTo(taskId);
238         assertWithMessage("Data").that(remoteTaskClient.getData()).asList()
239                 .containsExactlyElementsIn(new Byte[]{1, 2, 3, 4});
240     }
241 
242     @Test
testRemoteTaskRequested_mismatchedClientId()243     public void testRemoteTaskRequested_mismatchedClientId() throws Exception {
244         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1);
245         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
246         String serviceId = "serviceId_testing";
247         String vehicleId = "vehicleId_testing";
248         String processorId = "processorId_testing";
249         String clientId = "clientId_testing";
250         String misMatchedClientId = "clientId_mismatch";
251         String taskId = "taskId_testing";
252         byte[] data = new byte[]{1, 2, 3, 4};
253 
254         internalCallback.onClientRegistrationUpdated(
255                 new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId));
256         internalCallback.onRemoteTaskRequested(misMatchedClientId, taskId, data,
257                 /* taskMaximumDurationInSec= */ 10);
258 
259         assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isNull();
260         assertWithMessage("Data").that(remoteTaskClient.getData()).isNull();
261     }
262 
263     @Test
testReportRemoteTaskDone()264     public void testReportRemoteTaskDone() throws Exception {
265         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2);
266         String clientId = "clientId_testing";
267         String taskId = "taskId_testing";
268         prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null);
269 
270         mRemoteAccessManager.reportRemoteTaskDone(taskId);
271 
272         verify(mService).reportRemoteTaskDone(clientId, taskId);
273     }
274 
275     @Test
testReportRemoteTaskDone_nullTaskId()276     public void testReportRemoteTaskDone_nullTaskId() throws Exception {
277         assertThrows(IllegalArgumentException.class,
278                 () -> mRemoteAccessManager.reportRemoteTaskDone(/* taskId= */ null));
279     }
280 
281     @Test
testReportRemoteTaskDone_noRegisteredClient()282     public void testReportRemoteTaskDone_noRegisteredClient() throws Exception {
283         assertThrows(IllegalStateException.class,
284                 () -> mRemoteAccessManager.reportRemoteTaskDone("taskId_testing"));
285     }
286 
287     @Test
testReportRemoteTaskDone_invalidTaskId()288     public void testReportRemoteTaskDone_invalidTaskId() throws Exception {
289         RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2);
290         String clientId = "clientId_testing";
291         String taskId = "taskId_testing";
292         prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null);
293         doThrow(IllegalStateException.class).when(mService)
294                 .reportRemoteTaskDone(clientId, taskId);
295 
296         assertThrows(IllegalStateException.class,
297                 () -> mRemoteAccessManager.reportRemoteTaskDone(taskId));
298     }
299 
300     @Test
testSetPowerStatePostTaskExecution()301     public void testSetPowerStatePostTaskExecution() throws Exception {
302         int nextPowerState = CarRemoteAccessManager.NEXT_POWER_STATE_SUSPEND_TO_RAM;
303         boolean runGarageMode = true;
304 
305         mRemoteAccessManager.setPowerStatePostTaskExecution(nextPowerState, runGarageMode);
306 
307         verify(mService).setPowerStatePostTaskExecution(nextPowerState, runGarageMode);
308     }
309 
310     @Test
testOnShutdownStarting()311     public void testOnShutdownStarting() throws Exception {
312         RemoteTaskClientCallback remoteTaskClient = mock(RemoteTaskClientCallback.class);
313         String clientId = "clientId_testing";
314         String serviceId = "serviceId_testing";
315         String vehicleId = "vehicleId_testing";
316         String processorId = "processorId_testing";
317         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient);
318 
319         internalCallback.onClientRegistrationUpdated(
320                 new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId));
321 
322         verify(remoteTaskClient, timeout(DEFAULT_TIMEOUT)).onRegistrationUpdated(any());
323 
324         internalCallback.onShutdownStarting();
325 
326         verify(remoteTaskClient, timeout(DEFAULT_TIMEOUT)).onShutdownStarting(
327                 mFutureCaptor.capture());
328         CompletableRemoteTaskFuture future = mFutureCaptor.getValue();
329 
330         verify(mService, never()).confirmReadyForShutdown(any());
331 
332         future.complete();
333 
334         verify(mService).confirmReadyForShutdown(clientId);
335     }
336 
337     @Test
testScheduleInfoBuilder()338     public void testScheduleInfoBuilder() {
339         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE,
340                 TEST_START_TIME);
341         builder.setTaskData(TEST_TASK_DATA);
342         ScheduleInfo scheduleInfo = builder.setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC)
343                 .build();
344 
345         expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId())
346                 .isEqualTo(TEST_SCHEDULE_ID);
347         expectWithMessage("taskType from ScheduleInfo").that(scheduleInfo.getTaskType())
348                 .isEqualTo(TEST_TASK_TYPE);
349         expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData())
350                 .isEqualTo(TEST_TASK_DATA);
351         expectWithMessage("startTimeInEpochSeconds from ScheduleInfo")
352                 .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME);
353         expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount())
354                 .isEqualTo(TEST_TASK_COUNT);
355         expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic())
356                 .isEqualTo(TEST_PERIODIC);
357     }
358 
359     @Test
testScheduleInfoBuilder_enterGarageMode()360     public void testScheduleInfoBuilder_enterGarageMode() {
361         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID,
362                 TASK_TYPE_ENTER_GARAGE_MODE, TEST_START_TIME);
363         ScheduleInfo scheduleInfo = builder.setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC)
364                 .build();
365 
366         expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId())
367                 .isEqualTo(TEST_SCHEDULE_ID);
368         expectWithMessage("taskType from ScheduleInfo").that(scheduleInfo.getTaskType())
369                 .isEqualTo(TASK_TYPE_ENTER_GARAGE_MODE);
370         expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData())
371                 .isEmpty();
372         expectWithMessage("startTimeInEpochSeconds from ScheduleInfo")
373                 .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME);
374         expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount())
375                 .isEqualTo(TEST_TASK_COUNT);
376         expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic())
377                 .isEqualTo(TEST_PERIODIC);
378     }
379 
380     @Test
testScheduleInfoBuilder_invalidTaskType()381     public void testScheduleInfoBuilder_invalidTaskType() throws Exception {
382         assertThrows(IllegalArgumentException.class, () -> new ScheduleInfo.Builder(
383                 TEST_SCHEDULE_ID, /*taskType=*/-1234, TEST_START_TIME));
384     }
385 
386     @Test
testScheduleInfoBuilder_buildTwiceNotAllowed()387     public void testScheduleInfoBuilder_buildTwiceNotAllowed() {
388         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE,
389                 TEST_START_TIME);
390         builder.setTaskData(TEST_TASK_DATA).setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC)
391                 .build();
392 
393         assertThrows(IllegalStateException.class, () -> builder.build());
394     }
395 
396     @Test
testScheduleInfoBuilder_nullScheduleId()397     public void testScheduleInfoBuilder_nullScheduleId() {
398         assertThrows(IllegalArgumentException.class, () -> new ScheduleInfo.Builder(
399                 /* scheduleId= */ null, TEST_TASK_TYPE, TEST_START_TIME));
400     }
401 
402     @Test
testScheduleInfoBuilder_nullTaskData()403     public void testScheduleInfoBuilder_nullTaskData() {
404         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE,
405                 TEST_START_TIME);
406 
407         assertThrows(IllegalArgumentException.class, () -> builder.setTaskData(null));
408     }
409 
410     @Test
testScheduleInfoBuilder_negativeCount()411     public void testScheduleInfoBuilder_negativeCount() {
412         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID,
413                 TEST_TASK_TYPE, TEST_START_TIME);
414 
415         assertThrows(IllegalArgumentException.class, () -> builder.setCount(-1));
416     }
417 
418     @Test
testScheduleInfoBuilder_nullPeriodic()419     public void testScheduleInfoBuilder_nullPeriodic() {
420         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID,  TEST_TASK_TYPE,
421                 TEST_START_TIME);
422 
423         assertThrows(IllegalArgumentException.class, () -> builder.setPeriodic(null));
424     }
425 
426     @Test
testIsTaskScheduleSupported()427     public void testIsTaskScheduleSupported() throws Exception {
428         when(mService.isTaskScheduleSupported()).thenReturn(true);
429 
430         assertThat(mRemoteAccessManager.isTaskScheduleSupported()).isTrue();
431     }
432 
433     @Test
testGetInVehicleTaskScheduler()434     public void testGetInVehicleTaskScheduler() throws Exception {
435         when(mService.isTaskScheduleSupported()).thenReturn(true);
436 
437         assertThat(mRemoteAccessManager.getInVehicleTaskScheduler()).isNotNull();
438     }
439 
440     @Test
testGetInVehicleTaskScheduler_notSupported()441     public void testGetInVehicleTaskScheduler_notSupported() throws Exception {
442         when(mService.isTaskScheduleSupported()).thenReturn(false);
443 
444         assertThat(mRemoteAccessManager.getInVehicleTaskScheduler()).isNull();
445     }
446 
getTestTaskScheduleInfo()447     private TaskScheduleInfo getTestTaskScheduleInfo() {
448         TaskScheduleInfo taskScheduleInfo = new TaskScheduleInfo();
449         taskScheduleInfo.scheduleId = TEST_SCHEDULE_ID;
450         taskScheduleInfo.taskType = TEST_TASK_TYPE;
451         taskScheduleInfo.taskData = TEST_TASK_DATA;
452         taskScheduleInfo.startTimeInEpochSeconds = TEST_START_TIME;
453         taskScheduleInfo.count = TEST_TASK_COUNT;
454         taskScheduleInfo.periodicInSeconds = TEST_PERIODIC.getSeconds();
455         return taskScheduleInfo;
456     }
457 
458     @Test
testScheduleTask()459     public void testScheduleTask() throws Exception {
460         when(mService.isTaskScheduleSupported()).thenReturn(true);
461         ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE,
462                 TEST_START_TIME);
463         ScheduleInfo scheduleInfo = builder.setTaskData(TEST_TASK_DATA).setCount(TEST_TASK_COUNT)
464                 .setPeriodic(TEST_PERIODIC).build();
465 
466         mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(scheduleInfo);
467 
468         verify(mService).scheduleTask(getTestTaskScheduleInfo());
469     }
470 
471     @Test
testScheduleTask_nullScheduleInfo()472     public void testScheduleTask_nullScheduleInfo() throws Exception {
473         when(mService.isTaskScheduleSupported()).thenReturn(true);
474 
475         assertThrows(IllegalArgumentException.class, () ->
476                 mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(null));
477     }
478 
479     @Test
testScheduleTask_ServiceSpecificException()480     public void testScheduleTask_ServiceSpecificException() throws Exception {
481         when(mService.isTaskScheduleSupported()).thenReturn(true);
482         doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService)
483                 .scheduleTask(any());
484         ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID,
485                 TEST_TASK_TYPE, TEST_START_TIME).setTaskData(TEST_TASK_DATA)
486                 .setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC).build();
487 
488         assertThrows(InVehicleTaskSchedulerException.class, () ->
489                 mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(scheduleInfo));
490     }
491 
492     @Test
testUnscheduleTask()493     public void testUnscheduleTask() throws Exception {
494         when(mService.isTaskScheduleSupported()).thenReturn(true);
495 
496         mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(TEST_SCHEDULE_ID);
497 
498         verify(mService).unscheduleTask(TEST_SCHEDULE_ID);
499     }
500 
501     @Test
testUnscheduleTask_nullScheduleId()502     public void testUnscheduleTask_nullScheduleId() throws Exception {
503         when(mService.isTaskScheduleSupported()).thenReturn(true);
504 
505         assertThrows(IllegalArgumentException.class, () ->
506                 mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(null));
507     }
508 
509     @Test
testUnscheduleTask_ServiceSpecificException()510     public void testUnscheduleTask_ServiceSpecificException() throws Exception {
511         when(mService.isTaskScheduleSupported()).thenReturn(true);
512         doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService)
513                 .unscheduleTask(any());
514 
515         assertThrows(InVehicleTaskSchedulerException.class, () ->
516                 mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(TEST_SCHEDULE_ID));
517     }
518 
519     @Test
testGetAllPendingScheduledTasks()520     public void testGetAllPendingScheduledTasks() throws Exception {
521         when(mService.isTaskScheduleSupported()).thenReturn(true);
522         when(mService.getAllPendingScheduledTasks()).thenReturn(List.of(getTestTaskScheduleInfo()));
523 
524         List<ScheduleInfo> scheduleInfoList = mRemoteAccessManager.getInVehicleTaskScheduler()
525                 .getAllPendingScheduledTasks();
526 
527         assertThat(scheduleInfoList).hasSize(1);
528         ScheduleInfo scheduleInfo = scheduleInfoList.get(0);
529         expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId())
530                 .isEqualTo(TEST_SCHEDULE_ID);
531         expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData())
532                 .isEqualTo(TEST_TASK_DATA);
533         expectWithMessage("startTimeInEpochSeconds from ScheduleInfo")
534                 .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME);
535         expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount())
536                 .isEqualTo(TEST_TASK_COUNT);
537         expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic())
538                 .isEqualTo(TEST_PERIODIC);
539     }
540 
541     @Test
testGetAllPendingScheduledTasks_ServiceSpecificException()542     public void testGetAllPendingScheduledTasks_ServiceSpecificException() throws Exception {
543         when(mService.isTaskScheduleSupported()).thenReturn(true);
544         doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService)
545                 .getAllPendingScheduledTasks();
546 
547         assertThrows(InVehicleTaskSchedulerException.class, () ->
548                 mRemoteAccessManager.getInVehicleTaskScheduler().getAllPendingScheduledTasks());
549     }
550 
551     @Test
testUnscheduleAllTasks()552     public void testUnscheduleAllTasks() throws Exception {
553         when(mService.isTaskScheduleSupported()).thenReturn(true);
554 
555         mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleAllTasks();
556 
557         verify(mService).unscheduleAllTasks();
558     }
559 
560     @Test
testUnscheduleAllTasks_ServiceSpecificException()561     public void testUnscheduleAllTasks_ServiceSpecificException() throws Exception {
562         when(mService.isTaskScheduleSupported()).thenReturn(true);
563         doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService)
564                 .unscheduleAllTasks();
565 
566         assertThrows(InVehicleTaskSchedulerException.class, () ->
567                 mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleAllTasks());
568     }
569 
570     @Test
testIsTaskScheduled()571     public void testIsTaskScheduled() throws Exception {
572         when(mService.isTaskScheduleSupported()).thenReturn(true);
573         when(mService.isTaskScheduled(TEST_SCHEDULE_ID)).thenReturn(true);
574 
575         assertThat(mRemoteAccessManager.getInVehicleTaskScheduler()
576                 .isTaskScheduled(TEST_SCHEDULE_ID)).isTrue();
577     }
578 
579     @Test
testIsTaskScheduled_nullScheduleId()580     public void testIsTaskScheduled_nullScheduleId() throws Exception {
581         when(mService.isTaskScheduleSupported()).thenReturn(true);
582 
583         assertThrows(IllegalArgumentException.class, () ->
584                 mRemoteAccessManager.getInVehicleTaskScheduler().isTaskScheduled(null));
585     }
586 
587     @Test
testIsTaskScheduled_ServiceSpecificException()588     public void testIsTaskScheduled_ServiceSpecificException() throws Exception {
589         when(mService.isTaskScheduleSupported()).thenReturn(true);
590         when(mService.isTaskScheduled(any())).thenThrow(new ServiceSpecificException(
591                 SERVICE_ERROR_CODE_GENERAL));
592 
593         assertThrows(InVehicleTaskSchedulerException.class, () ->
594                 mRemoteAccessManager.getInVehicleTaskScheduler().isTaskScheduled(TEST_SCHEDULE_ID));
595     }
596 
597     @Test
testGetSupportedTaskTypes()598     public void testGetSupportedTaskTypes() throws Exception {
599         when(mService.isTaskScheduleSupported()).thenReturn(true);
600         int[] taskTypes = new int[]{TASK_TYPE_CUSTOM, TASK_TYPE_ENTER_GARAGE_MODE};
601         when(mService.getSupportedTaskTypesForScheduling()).thenReturn(taskTypes);
602 
603         assertThat(mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes())
604                 .isEqualTo(taskTypes);
605     }
606 
607     @Test
testGetSupportedTaskTypes_RemoteException()608     public void testGetSupportedTaskTypes_RemoteException() throws Exception {
609         when(mService.isTaskScheduleSupported()).thenReturn(true);
610         when(mService.getSupportedTaskTypesForScheduling()).thenThrow(new RemoteException());
611 
612         assertThat(mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes())
613                 .isEmpty();
614     }
615 
616     @Test
testGetSupportedTaskTypes_ServiceSpecificException()617     public void testGetSupportedTaskTypes_ServiceSpecificException() throws Exception {
618         when(mService.isTaskScheduleSupported()).thenReturn(true);
619         when(mService.getSupportedTaskTypesForScheduling()).thenThrow(
620                 new ServiceSpecificException(0));
621 
622         assertThrows(InVehicleTaskSchedulerException.class, () ->
623                 mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes());
624     }
625 
setClientAndGetCallback(RemoteTaskClientCallback client)626     private ICarRemoteAccessCallback setClientAndGetCallback(RemoteTaskClientCallback client)
627             throws Exception {
628         ArgumentCaptor<ICarRemoteAccessCallback> internalCallbackCaptor =
629                 ArgumentCaptor.forClass(ICarRemoteAccessCallback.class);
630         mRemoteAccessManager.setRemoteTaskClient(mExecutor, client);
631         verify(mService).addCarRemoteTaskClient(internalCallbackCaptor.capture());
632         return internalCallbackCaptor.getValue();
633     }
634 
prepareRemoteTaskRequested(RemoteTaskClient client, String clientId, String taskId, byte[] data)635     private void prepareRemoteTaskRequested(RemoteTaskClient client, String clientId,
636             String taskId, byte[] data) throws Exception {
637         ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(client);
638         String serviceId = "serviceId_testing";
639         String vehicleId = "vehicleId_testing";
640         String processorId = "processorId_testing";
641 
642         internalCallback.onClientRegistrationUpdated(
643                 new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId));
644         internalCallback.onRemoteTaskRequested(clientId, taskId, data,
645                 /* taskMaximumDurationInSec= */ 10);
646     }
647 
648     private static final class RemoteTaskClient implements RemoteTaskClientCallback {
649         private static final int DEFAULT_TIMEOUT = 3000;
650 
651         private final CountDownLatch mLatch;
652         private String mServiceId;
653         private String mVehicleId;
654         private String mProcessorId;
655         private String mClientId;
656         private String mTaskId;
657         private boolean mRegistrationFailed;
658         private boolean mServerlessClientRegistered;
659         private byte[] mData;
660 
RemoteTaskClient(int expectedCallbackCount)661         private RemoteTaskClient(int expectedCallbackCount) {
662             mLatch = new CountDownLatch(expectedCallbackCount);
663         }
664 
665         @Override
onRegistrationUpdated(RemoteTaskClientRegistrationInfo info)666         public void onRegistrationUpdated(RemoteTaskClientRegistrationInfo info) {
667             mServiceId = info.getServiceId();
668             mVehicleId = info.getVehicleId();
669             mProcessorId = info.getProcessorId();
670             mClientId = info.getClientId();
671             mLatch.countDown();
672         }
673 
674         @Override
onServerlessClientRegistered()675         public void onServerlessClientRegistered() {
676             mServerlessClientRegistered = true;
677             mLatch.countDown();
678         }
679 
680         @Override
onRegistrationFailed()681         public void onRegistrationFailed() {
682             mRegistrationFailed = true;
683             mLatch.countDown();
684         }
685 
686         @Override
onRemoteTaskRequested(String taskId, byte[] data, int remainingTimeSec)687         public void onRemoteTaskRequested(String taskId, byte[] data, int remainingTimeSec) {
688             mTaskId = taskId;
689             mData = data;
690             mLatch.countDown();
691         }
692 
693         @Override
onShutdownStarting(CarRemoteAccessManager.CompletableRemoteTaskFuture future)694         public void onShutdownStarting(CarRemoteAccessManager.CompletableRemoteTaskFuture future) {
695             mLatch.countDown();
696         }
697 
getServiceId()698         public String getServiceId() throws Exception {
699             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
700             return mServiceId;
701         }
702 
getVehicleId()703         public String getVehicleId() throws Exception {
704             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
705             return mVehicleId;
706         }
707 
getProcessorId()708         public String getProcessorId() throws Exception {
709             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
710             return mProcessorId;
711         }
712 
getClientId()713         public String getClientId() throws Exception {
714             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
715             return mClientId;
716         }
717 
getTaskId()718         public String getTaskId() throws Exception {
719             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
720             return mTaskId;
721         }
722 
getData()723         public byte[] getData() throws Exception {
724             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
725             return mData;
726         }
727 
isRegistrationFail()728         public boolean isRegistrationFail() throws Exception {
729             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
730             return mRegistrationFailed;
731         }
732 
isServerlessClientRegistered()733         public boolean isServerlessClientRegistered() throws Exception {
734             JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT);
735             return mServerlessClientRegistered;
736         }
737     }
738 }
739