/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.remoteaccess; import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_CUSTOM; import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_ENTER_GARAGE_MODE; import static android.car.remoteaccess.ICarRemoteAccessService.SERVICE_ERROR_CODE_GENERAL; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.car.feature.FakeFeatureFlagsImpl; import android.car.feature.Flags; import android.car.remoteaccess.CarRemoteAccessManager.CompletableRemoteTaskFuture; import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskSchedulerException; import android.car.remoteaccess.CarRemoteAccessManager.RemoteTaskClientCallback; import android.car.remoteaccess.CarRemoteAccessManager.ScheduleInfo; import android.car.remoteaccess.CarRemoteAccessManager.TaskType; import android.car.test.AbstractExpectableTestCase; import android.car.test.mocks.JavaMockitoHelper; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.platform.test.ravenwood.RavenwoodRule; import com.android.car.internal.ICarBase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; /** * <p>This class contains unit tests for the {@link CarRemoteAccessManager}. */ @RunWith(MockitoJUnitRunner.class) public final class CarRemoteAccessManagerUnitTest extends AbstractExpectableTestCase { private static final int DEFAULT_TIMEOUT = 3000; private static final String TEST_SCHEDULE_ID = "test schedule id"; private static final byte[] TEST_TASK_DATA = new byte[]{ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef}; private static final @TaskType int TEST_TASK_TYPE = TASK_TYPE_CUSTOM; private static final long TEST_START_TIME = 1234; private static final int TEST_TASK_COUNT = 10; private static final Duration TEST_PERIODIC = Duration.ofSeconds(1); @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().setProcessApp().build(); @Mock private ICarBase mCar; @Mock private IBinder mBinder; @Mock private ICarRemoteAccessService mService; @Captor private ArgumentCaptor<CompletableRemoteTaskFuture> mFutureCaptor; private CarRemoteAccessManager mRemoteAccessManager; private final Executor mExecutor = Runnable::run; @Before public void setUp() throws Exception { when(mBinder.queryLocalInterface(anyString())).thenReturn(mService); when(mCar.handleRemoteExceptionFromCarService(any(RemoteException.class), any())) .thenAnswer((inv) -> { return inv.getArgument(1); }); mRemoteAccessManager = new CarRemoteAccessManager(mCar, mBinder); } @Test public void testSetRemoteTaskClient() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0); mRemoteAccessManager.setRemoteTaskClient(mExecutor, remoteTaskClient); verify(mService).addCarRemoteTaskClient(any(ICarRemoteAccessCallback.class)); } @Test public void testSetRemoteTaskClient_invalidArguments() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0); assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.setRemoteTaskClient( /* executor= */ null, remoteTaskClient)); assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.setRemoteTaskClient( mExecutor, /* callback= */ null)); } @Test public void testSetRemoteTaskClient_doubleRegistration() throws Exception { RemoteTaskClient remoteTaskClientOne = new RemoteTaskClient(/* expectedCallbackCount= */ 0); RemoteTaskClient remoteTaskClientTwo = new RemoteTaskClient(/* expectedCallbackCount= */ 0); mRemoteAccessManager.setRemoteTaskClient(mExecutor, remoteTaskClientOne); assertThrows(IllegalStateException.class, () -> mRemoteAccessManager.setRemoteTaskClient( mExecutor, remoteTaskClientTwo)); } @Test public void testSetRmoteTaskClient_remoteException() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0); doThrow(RemoteException.class).when(mService) .addCarRemoteTaskClient(any(ICarRemoteAccessCallback.class)); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing", /* data= */ null, /* taskMaxDurationInSec= */ 10); assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull(); } @Test public void testClearRemoteTaskClient() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); mRemoteAccessManager.clearRemoteTaskClient(); internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing", /* data= */ null, /* taskMaxDurationInSec= */ 10); assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull(); } @Test public void testClearRemoteTaskClient_remoteException() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 0); doThrow(RemoteException.class).when(mService) .removeCarRemoteTaskClient(any(ICarRemoteAccessCallback.class)); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); mRemoteAccessManager.clearRemoteTaskClient(); internalCallback.onRemoteTaskRequested("clientId_testing", "taskId_testing", /* data= */ null, /* taskMaxDurationInSec= */ 10); assertWithMessage("Remote task").that(remoteTaskClient.getTaskId()).isNull(); } @Test public void testClientRegistration() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); String serviceId = "serviceId_testing"; String vehicleId = "vehicleId_testing"; String processorId = "processorId_testing"; String clientId = "clientId_testing"; internalCallback.onClientRegistrationUpdated( new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId)); assertWithMessage("Service ID").that(remoteTaskClient.getServiceId()).isEqualTo(serviceId); assertWithMessage("Vehicle ID").that(remoteTaskClient.getVehicleId()).isEqualTo(vehicleId); assertWithMessage("Processor ID").that(remoteTaskClient.getProcessorId()) .isEqualTo(processorId); assertWithMessage("Client ID").that(remoteTaskClient.getClientId()).isEqualTo(clientId); } @Test public void testServerlessClientRegistration() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); String clientId = "clientId_testing"; FakeFeatureFlagsImpl fakeFlagsImpl = new FakeFeatureFlagsImpl(); fakeFlagsImpl.setFlag(Flags.FLAG_SERVERLESS_REMOTE_ACCESS, true); mRemoteAccessManager.setFeatureFlags(fakeFlagsImpl); internalCallback.onServerlessClientRegistered(clientId); assertThat(remoteTaskClient.isServerlessClientRegistered()).isTrue(); } @Test public void testClientRegistrationFail() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); internalCallback.onClientRegistrationFailed(); assertWithMessage("Registration fail").that(remoteTaskClient.isRegistrationFail()).isTrue(); } @Test public void testRemoteTaskRequested() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2); String clientId = "clientId_testing"; String taskId = "taskId_testing"; prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null); assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isEqualTo(taskId); assertWithMessage("Data").that(remoteTaskClient.getData()).isNull(); } @Test public void testRemoteTaskRequested_withData() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2); String clientId = "clientId_testing"; String taskId = "taskId_testing"; byte[] data = new byte[]{1, 2, 3, 4}; prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, data); assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isEqualTo(taskId); assertWithMessage("Data").that(remoteTaskClient.getData()).asList() .containsExactlyElementsIn(new Byte[]{1, 2, 3, 4}); } @Test public void testRemoteTaskRequested_mismatchedClientId() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 1); ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); String serviceId = "serviceId_testing"; String vehicleId = "vehicleId_testing"; String processorId = "processorId_testing"; String clientId = "clientId_testing"; String misMatchedClientId = "clientId_mismatch"; String taskId = "taskId_testing"; byte[] data = new byte[]{1, 2, 3, 4}; internalCallback.onClientRegistrationUpdated( new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId)); internalCallback.onRemoteTaskRequested(misMatchedClientId, taskId, data, /* taskMaximumDurationInSec= */ 10); assertWithMessage("Task ID").that(remoteTaskClient.getTaskId()).isNull(); assertWithMessage("Data").that(remoteTaskClient.getData()).isNull(); } @Test public void testReportRemoteTaskDone() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2); String clientId = "clientId_testing"; String taskId = "taskId_testing"; prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null); mRemoteAccessManager.reportRemoteTaskDone(taskId); verify(mService).reportRemoteTaskDone(clientId, taskId); } @Test public void testReportRemoteTaskDone_nullTaskId() throws Exception { assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.reportRemoteTaskDone(/* taskId= */ null)); } @Test public void testReportRemoteTaskDone_noRegisteredClient() throws Exception { assertThrows(IllegalStateException.class, () -> mRemoteAccessManager.reportRemoteTaskDone("taskId_testing")); } @Test public void testReportRemoteTaskDone_invalidTaskId() throws Exception { RemoteTaskClient remoteTaskClient = new RemoteTaskClient(/* expectedCallbackCount= */ 2); String clientId = "clientId_testing"; String taskId = "taskId_testing"; prepareRemoteTaskRequested(remoteTaskClient, clientId, taskId, /* data= */ null); doThrow(IllegalStateException.class).when(mService) .reportRemoteTaskDone(clientId, taskId); assertThrows(IllegalStateException.class, () -> mRemoteAccessManager.reportRemoteTaskDone(taskId)); } @Test public void testSetPowerStatePostTaskExecution() throws Exception { int nextPowerState = CarRemoteAccessManager.NEXT_POWER_STATE_SUSPEND_TO_RAM; boolean runGarageMode = true; mRemoteAccessManager.setPowerStatePostTaskExecution(nextPowerState, runGarageMode); verify(mService).setPowerStatePostTaskExecution(nextPowerState, runGarageMode); } @Test public void testOnShutdownStarting() throws Exception { RemoteTaskClientCallback remoteTaskClient = mock(RemoteTaskClientCallback.class); String clientId = "clientId_testing"; String serviceId = "serviceId_testing"; String vehicleId = "vehicleId_testing"; String processorId = "processorId_testing"; ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(remoteTaskClient); internalCallback.onClientRegistrationUpdated( new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId)); verify(remoteTaskClient, timeout(DEFAULT_TIMEOUT)).onRegistrationUpdated(any()); internalCallback.onShutdownStarting(); verify(remoteTaskClient, timeout(DEFAULT_TIMEOUT)).onShutdownStarting( mFutureCaptor.capture()); CompletableRemoteTaskFuture future = mFutureCaptor.getValue(); verify(mService, never()).confirmReadyForShutdown(any()); future.complete(); verify(mService).confirmReadyForShutdown(clientId); } @Test public void testScheduleInfoBuilder() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); builder.setTaskData(TEST_TASK_DATA); ScheduleInfo scheduleInfo = builder.setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC) .build(); expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId()) .isEqualTo(TEST_SCHEDULE_ID); expectWithMessage("taskType from ScheduleInfo").that(scheduleInfo.getTaskType()) .isEqualTo(TEST_TASK_TYPE); expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData()) .isEqualTo(TEST_TASK_DATA); expectWithMessage("startTimeInEpochSeconds from ScheduleInfo") .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME); expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount()) .isEqualTo(TEST_TASK_COUNT); expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic()) .isEqualTo(TEST_PERIODIC); } @Test public void testScheduleInfoBuilder_enterGarageMode() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TASK_TYPE_ENTER_GARAGE_MODE, TEST_START_TIME); ScheduleInfo scheduleInfo = builder.setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC) .build(); expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId()) .isEqualTo(TEST_SCHEDULE_ID); expectWithMessage("taskType from ScheduleInfo").that(scheduleInfo.getTaskType()) .isEqualTo(TASK_TYPE_ENTER_GARAGE_MODE); expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData()) .isEmpty(); expectWithMessage("startTimeInEpochSeconds from ScheduleInfo") .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME); expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount()) .isEqualTo(TEST_TASK_COUNT); expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic()) .isEqualTo(TEST_PERIODIC); } @Test public void testScheduleInfoBuilder_invalidTaskType() throws Exception { assertThrows(IllegalArgumentException.class, () -> new ScheduleInfo.Builder( TEST_SCHEDULE_ID, /*taskType=*/-1234, TEST_START_TIME)); } @Test public void testScheduleInfoBuilder_buildTwiceNotAllowed() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); builder.setTaskData(TEST_TASK_DATA).setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC) .build(); assertThrows(IllegalStateException.class, () -> builder.build()); } @Test public void testScheduleInfoBuilder_nullScheduleId() { assertThrows(IllegalArgumentException.class, () -> new ScheduleInfo.Builder( /* scheduleId= */ null, TEST_TASK_TYPE, TEST_START_TIME)); } @Test public void testScheduleInfoBuilder_nullTaskData() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); assertThrows(IllegalArgumentException.class, () -> builder.setTaskData(null)); } @Test public void testScheduleInfoBuilder_negativeCount() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); assertThrows(IllegalArgumentException.class, () -> builder.setCount(-1)); } @Test public void testScheduleInfoBuilder_nullPeriodic() { ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); assertThrows(IllegalArgumentException.class, () -> builder.setPeriodic(null)); } @Test public void testIsTaskScheduleSupported() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); assertThat(mRemoteAccessManager.isTaskScheduleSupported()).isTrue(); } @Test public void testGetInVehicleTaskScheduler() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); assertThat(mRemoteAccessManager.getInVehicleTaskScheduler()).isNotNull(); } @Test public void testGetInVehicleTaskScheduler_notSupported() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(false); assertThat(mRemoteAccessManager.getInVehicleTaskScheduler()).isNull(); } private TaskScheduleInfo getTestTaskScheduleInfo() { TaskScheduleInfo taskScheduleInfo = new TaskScheduleInfo(); taskScheduleInfo.scheduleId = TEST_SCHEDULE_ID; taskScheduleInfo.taskType = TEST_TASK_TYPE; taskScheduleInfo.taskData = TEST_TASK_DATA; taskScheduleInfo.startTimeInEpochSeconds = TEST_START_TIME; taskScheduleInfo.count = TEST_TASK_COUNT; taskScheduleInfo.periodicInSeconds = TEST_PERIODIC.getSeconds(); return taskScheduleInfo; } @Test public void testScheduleTask() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); ScheduleInfo.Builder builder = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME); ScheduleInfo scheduleInfo = builder.setTaskData(TEST_TASK_DATA).setCount(TEST_TASK_COUNT) .setPeriodic(TEST_PERIODIC).build(); mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(scheduleInfo); verify(mService).scheduleTask(getTestTaskScheduleInfo()); } @Test public void testScheduleTask_nullScheduleInfo() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(null)); } @Test public void testScheduleTask_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService) .scheduleTask(any()); ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID, TEST_TASK_TYPE, TEST_START_TIME).setTaskData(TEST_TASK_DATA) .setCount(TEST_TASK_COUNT).setPeriodic(TEST_PERIODIC).build(); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().scheduleTask(scheduleInfo)); } @Test public void testUnscheduleTask() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(TEST_SCHEDULE_ID); verify(mService).unscheduleTask(TEST_SCHEDULE_ID); } @Test public void testUnscheduleTask_nullScheduleId() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(null)); } @Test public void testUnscheduleTask_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService) .unscheduleTask(any()); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleTask(TEST_SCHEDULE_ID)); } @Test public void testGetAllPendingScheduledTasks() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); when(mService.getAllPendingScheduledTasks()).thenReturn(List.of(getTestTaskScheduleInfo())); List<ScheduleInfo> scheduleInfoList = mRemoteAccessManager.getInVehicleTaskScheduler() .getAllPendingScheduledTasks(); assertThat(scheduleInfoList).hasSize(1); ScheduleInfo scheduleInfo = scheduleInfoList.get(0); expectWithMessage("scheduleId from ScheduleInfo").that(scheduleInfo.getScheduleId()) .isEqualTo(TEST_SCHEDULE_ID); expectWithMessage("taskData from ScheduleInfo").that(scheduleInfo.getTaskData()) .isEqualTo(TEST_TASK_DATA); expectWithMessage("startTimeInEpochSeconds from ScheduleInfo") .that(scheduleInfo.getStartTimeInEpochSeconds()).isEqualTo(TEST_START_TIME); expectWithMessage("count from ScheduleInfo").that(scheduleInfo.getCount()) .isEqualTo(TEST_TASK_COUNT); expectWithMessage("periodic from ScheduleInfo").that(scheduleInfo.getPeriodic()) .isEqualTo(TEST_PERIODIC); } @Test public void testGetAllPendingScheduledTasks_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService) .getAllPendingScheduledTasks(); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().getAllPendingScheduledTasks()); } @Test public void testUnscheduleAllTasks() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleAllTasks(); verify(mService).unscheduleAllTasks(); } @Test public void testUnscheduleAllTasks_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); doThrow(new ServiceSpecificException(SERVICE_ERROR_CODE_GENERAL)).when(mService) .unscheduleAllTasks(); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().unscheduleAllTasks()); } @Test public void testIsTaskScheduled() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); when(mService.isTaskScheduled(TEST_SCHEDULE_ID)).thenReturn(true); assertThat(mRemoteAccessManager.getInVehicleTaskScheduler() .isTaskScheduled(TEST_SCHEDULE_ID)).isTrue(); } @Test public void testIsTaskScheduled_nullScheduleId() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); assertThrows(IllegalArgumentException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().isTaskScheduled(null)); } @Test public void testIsTaskScheduled_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); when(mService.isTaskScheduled(any())).thenThrow(new ServiceSpecificException( SERVICE_ERROR_CODE_GENERAL)); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().isTaskScheduled(TEST_SCHEDULE_ID)); } @Test public void testGetSupportedTaskTypes() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); int[] taskTypes = new int[]{TASK_TYPE_CUSTOM, TASK_TYPE_ENTER_GARAGE_MODE}; when(mService.getSupportedTaskTypesForScheduling()).thenReturn(taskTypes); assertThat(mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes()) .isEqualTo(taskTypes); } @Test public void testGetSupportedTaskTypes_RemoteException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); when(mService.getSupportedTaskTypesForScheduling()).thenThrow(new RemoteException()); assertThat(mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes()) .isEmpty(); } @Test public void testGetSupportedTaskTypes_ServiceSpecificException() throws Exception { when(mService.isTaskScheduleSupported()).thenReturn(true); when(mService.getSupportedTaskTypesForScheduling()).thenThrow( new ServiceSpecificException(0)); assertThrows(InVehicleTaskSchedulerException.class, () -> mRemoteAccessManager.getInVehicleTaskScheduler().getSupportedTaskTypes()); } private ICarRemoteAccessCallback setClientAndGetCallback(RemoteTaskClientCallback client) throws Exception { ArgumentCaptor<ICarRemoteAccessCallback> internalCallbackCaptor = ArgumentCaptor.forClass(ICarRemoteAccessCallback.class); mRemoteAccessManager.setRemoteTaskClient(mExecutor, client); verify(mService).addCarRemoteTaskClient(internalCallbackCaptor.capture()); return internalCallbackCaptor.getValue(); } private void prepareRemoteTaskRequested(RemoteTaskClient client, String clientId, String taskId, byte[] data) throws Exception { ICarRemoteAccessCallback internalCallback = setClientAndGetCallback(client); String serviceId = "serviceId_testing"; String vehicleId = "vehicleId_testing"; String processorId = "processorId_testing"; internalCallback.onClientRegistrationUpdated( new RemoteTaskClientRegistrationInfo(serviceId, vehicleId, processorId, clientId)); internalCallback.onRemoteTaskRequested(clientId, taskId, data, /* taskMaximumDurationInSec= */ 10); } private static final class RemoteTaskClient implements RemoteTaskClientCallback { private static final int DEFAULT_TIMEOUT = 3000; private final CountDownLatch mLatch; private String mServiceId; private String mVehicleId; private String mProcessorId; private String mClientId; private String mTaskId; private boolean mRegistrationFailed; private boolean mServerlessClientRegistered; private byte[] mData; private RemoteTaskClient(int expectedCallbackCount) { mLatch = new CountDownLatch(expectedCallbackCount); } @Override public void onRegistrationUpdated(RemoteTaskClientRegistrationInfo info) { mServiceId = info.getServiceId(); mVehicleId = info.getVehicleId(); mProcessorId = info.getProcessorId(); mClientId = info.getClientId(); mLatch.countDown(); } @Override public void onServerlessClientRegistered() { mServerlessClientRegistered = true; mLatch.countDown(); } @Override public void onRegistrationFailed() { mRegistrationFailed = true; mLatch.countDown(); } @Override public void onRemoteTaskRequested(String taskId, byte[] data, int remainingTimeSec) { mTaskId = taskId; mData = data; mLatch.countDown(); } @Override public void onShutdownStarting(CarRemoteAccessManager.CompletableRemoteTaskFuture future) { mLatch.countDown(); } public String getServiceId() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mServiceId; } public String getVehicleId() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mVehicleId; } public String getProcessorId() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mProcessorId; } public String getClientId() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mClientId; } public String getTaskId() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mTaskId; } public byte[] getData() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mData; } public boolean isRegistrationFail() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mRegistrationFailed; } public boolean isServerlessClientRegistered() throws Exception { JavaMockitoHelper.await(mLatch, DEFAULT_TIMEOUT); return mServerlessClientRegistered; } } }