Add InVehicleTaskScheduler CTS tests.

Add CTS test cases for
android.car.remoteaccess.CarRemoteAccessManager$InVehicleTaskScheduler.

The test cases will be skipped if the in vehicle task scheduler is
not supported.

Test: atest android.car.cts.CarRemoteAccessManagerTest
Run the test manually on gcar_emu with reference remote access HAL
implementation with and without a host simulated TCU connected.
Bug: 299968417

Change-Id: If6b2e561a381ded50832fe1365964616a9821885
diff --git a/tests/tests/car/src/android/car/cts/CarRemoteAccessManagerTest.java b/tests/tests/car/src/android/car/cts/CarRemoteAccessManagerTest.java
index 09e3a60..e7d0d7e 100644
--- a/tests/tests/car/src/android/car/cts/CarRemoteAccessManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarRemoteAccessManagerTest.java
@@ -16,19 +16,30 @@
 
 package android.car.cts;
 
+import static android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS;
+
 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.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeNoException;
 import static org.junit.Assume.assumeTrue;
 
 import android.car.Car;
+import android.car.feature.Flags;
 import android.car.remoteaccess.CarRemoteAccessManager;
 import android.car.remoteaccess.CarRemoteAccessManager.CompletableRemoteTaskFuture;
+import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler;
+import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskSchedulerException;
 import android.car.remoteaccess.CarRemoteAccessManager.RemoteTaskClientCallback;
+import android.car.remoteaccess.CarRemoteAccessManager.ScheduleInfo;
 import android.car.remoteaccess.RemoteTaskClientRegistrationInfo;
+import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -42,9 +53,14 @@
 import com.android.compatibility.common.util.ProtoUtils;
 import com.android.internal.annotations.GuardedBy;
 
+import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 @AppModeFull(reason = "Instant Apps cannot get car related permissions")
@@ -56,9 +72,17 @@
     private static final String DUMP_COMMAND =
             "dumpsys car_service --services CarRemoteAccessService --proto";
     private static final String SERVERLESS_CLIENT_ID = "TestServerlessClientId";
+    private static final String TEST_SCHEDULE_ID_1 = "TestScheduleId1";
+    private static final String TEST_SCHEDULE_ID_2 = "TestScheduleId2";
+    private static final byte[] TEST_TASK_DATA = "test data".getBytes();
 
     private Executor mExecutor;
     private CarRemoteAccessManager mCarRemoteAccessManager;
+    private boolean mServerlessRemoteTaskClientSet = false;
+    private String mPackageName;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -69,26 +93,14 @@
         mCarRemoteAccessManager = (CarRemoteAccessManager) getCar()
                 .getCarManager(Car.CAR_REMOTE_ACCESS_SERVICE);
         assertThat(mCarRemoteAccessManager).isNotNull();
+        mPackageName = mContext.getPackageName();
     }
 
-    private boolean isTestPkgServerlessClient() {
-        try {
-            CarRemoteAccessDumpProto dump = ProtoUtils.getProto(
-                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                    CarRemoteAccessDumpProto.class, DUMP_COMMAND);
-
-            for  (int i = 0; i < dump.getServerlessClientsCount(); i++) {
-                ServerlessClientInfo serverlessClientInfo = dump.getServerlessClients(i);
-                if (serverlessClientInfo.getPackageName().equals(mContext.getPackageName())) {
-                    return true;
-                }
-            }
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to check whether this test is a serverless remote task client"
-                    + ", default to false", e);
+    @After
+    public void tearDown() throws Exception {
+        if (mServerlessRemoteTaskClientSet) {
+            mCarRemoteAccessManager.removeServerlessRemoteTaskClient(mPackageName);
         }
-
-        return false;
     }
 
     @Test
@@ -113,24 +125,17 @@
     @ApiTest(apis = {
             "android.car.remoteaccess.CarRemoteAccessManager#setRemoteTaskClient"
     })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
     public void testSetRemoteTaskClient_serverlessClient() throws Exception {
-        String packageName = mContext.getPackageName();
-        mCarRemoteAccessManager.addServerlessRemoteTaskClient(packageName, SERVERLESS_CLIENT_ID);
+        setSelfAsServerlessClient();
 
-        assertWithMessage(
-                "This test requires the test package to be a serverless remote task client").that(
-                isTestPkgServerlessClient()).isTrue();
+        RemoteTaskClientCallbackImpl callback = new RemoteTaskClientCallbackImpl();
 
-        try {
-            RemoteTaskClientCallbackImpl callback = new RemoteTaskClientCallbackImpl();
+        mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callback);
 
-            mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callback);
-
-            PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS,
-                    () -> callback.isServerlessClientRegistered());
-        } finally {
-            mCarRemoteAccessManager.removeServerlessRemoteTaskClient(packageName);
-        }
+        PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS,
+                () -> callback.isServerlessClientRegistered());
     }
 
     /**
@@ -184,12 +189,248 @@
     @Test
     @ApiTest(apis = {
             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
     })
-    public void testScheduleTask() {
-        assumeTrue("Task scheduling is not supported, skipping the test",
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testGetInVehicleTaskScheduler_notSupported() {
+        assumeFalse("Task scheduling is supported, skipping the test",
                 mCarRemoteAccessManager.isTaskScheduleSupported());
 
-        // TODO(b/282792374): Implement this.
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        assertWithMessage("InVehicleTaskScheduler must be null when task schedule is not supported")
+                .that(taskScheduler).isNull();
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testGetInVehicleTaskScheduler_isSupported() {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        assertWithMessage("InVehicleTaskScheduler must not be null when task schedule is supported")
+                .that(taskScheduler).isNotNull();
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleAllTasks",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testScheduleTask() throws Exception {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        // Schedule the task to be executed 30s later.
+        long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
+        ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1, TEST_TASK_DATA,
+                startTimeInEpochSeconds).build();
+        try {
+            taskScheduler.scheduleTask(scheduleInfo);
+        } catch (InVehicleTaskSchedulerException e) {
+            assumeNoException("Assume task schedule to succeed", e);
+        }
+
+        taskScheduler.unscheduleAllTasks();
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleAllTasks",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testScheduleTask_duplicateScheduleIdMustThrowException() throws Exception {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        // Schedule the task to be executed 30s later.
+        long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
+        ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1, TEST_TASK_DATA,
+                startTimeInEpochSeconds).build();
+        try {
+            taskScheduler.scheduleTask(scheduleInfo);
+        } catch (InVehicleTaskSchedulerException e) {
+            assumeNoException("Assume task schedule to succeed", e);
+        }
+
+        try {
+            // Schedule the same task twice must cause IllegalArgumentException.
+            assertThrows(IllegalArgumentException.class, () -> taskScheduler.scheduleTask(
+                    scheduleInfo));
+        } finally {
+            taskScheduler.unscheduleAllTasks();
+        }
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleAllTasks",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "isTaskScheduled",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testIsTaskScheduled() throws Exception {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        // Schedule the task to be executed 30s later.
+        long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
+        ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1, TEST_TASK_DATA,
+                startTimeInEpochSeconds).build();
+        try {
+            taskScheduler.scheduleTask(scheduleInfo);
+        } catch (InVehicleTaskSchedulerException e) {
+            assumeNoException("Assume task schedule to succeed", e);
+        }
+
+        try {
+            expectWithMessage("isTaskScheduled for scheduled task").that(
+                    taskScheduler.isTaskScheduled(TEST_SCHEDULE_ID_1)).isTrue();
+            expectWithMessage("isTaskScheduled for unscheduled task").that(
+                    taskScheduler.isTaskScheduled(TEST_SCHEDULE_ID_2)).isFalse();
+        } finally {
+            taskScheduler.unscheduleAllTasks();
+        }
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleAllTasks",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "getAllScheduledTasks",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testGetAllScheduledTasks() throws Exception {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        // Schedule the task to be executed 30s later.
+        long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
+        ScheduleInfo scheduleInfo1 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1, TEST_TASK_DATA,
+                startTimeInEpochSeconds).build();
+        ScheduleInfo scheduleInfo2 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_2, TEST_TASK_DATA,
+                startTimeInEpochSeconds).setCount(2).setPeriodic(Duration.ofSeconds(30)).build();
+
+        try {
+            taskScheduler.unscheduleAllTasks();
+            taskScheduler.scheduleTask(scheduleInfo1);
+            taskScheduler.scheduleTask(scheduleInfo2);
+        } catch (InVehicleTaskSchedulerException e) {
+            assumeNoException("Assume task schedule to succeed", e);
+        }
+
+        try {
+            List<ScheduleInfo> scheduleInfo = taskScheduler.getAllScheduledTasks();
+
+            assertWithMessage("Must return two scheduled tasks").that(scheduleInfo).hasSize(2);
+            List<String> gotScheduleIds = new ArrayList<>();
+            for (int i = 0; i < scheduleInfo.size(); i++) {
+                ScheduleInfo info = scheduleInfo.get(i);
+                expectWithMessage("Got expected scheduleId").that(info.getScheduleId())
+                        .isAnyOf(TEST_SCHEDULE_ID_1, TEST_SCHEDULE_ID_2);
+                gotScheduleIds.add(info.getScheduleId());
+                expectWithMessage("Got expected task data").that(info.getTaskData()).isEqualTo(
+                        TEST_TASK_DATA);
+                if (info.getScheduleId().equals(TEST_SCHEDULE_ID_1)) {
+                    expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(1);
+                    expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
+                            Duration.ZERO);
+                } else {
+                    expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(2);
+                    expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
+                            Duration.ofSeconds(30));
+                }
+            }
+            expectWithMessage("Got all expected schedule Ids").that(gotScheduleIds).containsExactly(
+                    TEST_SCHEDULE_ID_1, TEST_SCHEDULE_ID_2);
+        } finally {
+            taskScheduler.unscheduleAllTasks();
+        }
+    }
+
+    @Test
+    @ApiTest(apis = {
+            "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
+            "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleAllTasks",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "getAllScheduledTasks",
+            "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
+                    + "unscheduleTask",
+    })
+    @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
+    @RequiresFlagsEnabled(Flags.FLAG_SERVERLESS_REMOTE_ACCESS)
+    public void testUnscheduleTask() throws Exception {
+        assumeTaskSchedulingSupported();
+        setSelfAsServerlessClient();
+
+        InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
+        // Schedule the task to be executed 30s later.
+        long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
+        ScheduleInfo scheduleInfo1 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1, TEST_TASK_DATA,
+                startTimeInEpochSeconds).build();
+        ScheduleInfo scheduleInfo2 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_2, TEST_TASK_DATA,
+                startTimeInEpochSeconds).setCount(2).setPeriodic(Duration.ofSeconds(30)).build();
+
+        try {
+            taskScheduler.unscheduleAllTasks();
+            taskScheduler.scheduleTask(scheduleInfo1);
+            taskScheduler.scheduleTask(scheduleInfo2);
+        } catch (InVehicleTaskSchedulerException e) {
+            assumeNoException("Assume task schedule to succeed", e);
+        }
+
+        taskScheduler.unscheduleTask(TEST_SCHEDULE_ID_2);
+
+        try {
+            List<ScheduleInfo> scheduleInfo = taskScheduler.getAllScheduledTasks();
+
+            assertWithMessage("Must return one scheduled tasks").that(scheduleInfo).hasSize(1);
+            ScheduleInfo info = scheduleInfo.get(0);
+            expectWithMessage("Got expected scheduleId").that(info.getScheduleId())
+                    .isEqualTo(TEST_SCHEDULE_ID_1);
+            expectWithMessage("Got expected task data").that(info.getTaskData()).isEqualTo(
+                    TEST_TASK_DATA);
+            expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(1);
+            expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
+                    Duration.ZERO);
+        } finally {
+            taskScheduler.unscheduleAllTasks();
+        }
     }
 
     private static final class RemoteTaskClientCallbackImpl implements RemoteTaskClientCallback {
@@ -270,4 +511,44 @@
             }
         }
     }
+
+    private boolean isTestPkgServerlessClient() {
+        try {
+            CarRemoteAccessDumpProto dump = ProtoUtils.getProto(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    CarRemoteAccessDumpProto.class, DUMP_COMMAND);
+
+            for (int i = 0; i < dump.getServerlessClientsCount(); i++) {
+                ServerlessClientInfo serverlessClientInfo = dump.getServerlessClients(i);
+                if (serverlessClientInfo.getPackageName().equals(mContext.getPackageName())) {
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to check whether this test is a serverless remote task client"
+                    + ", default to false", e);
+        }
+
+        return false;
+    }
+
+    private void setSelfAsServerlessClient() {
+        try {
+            mCarRemoteAccessManager.addServerlessRemoteTaskClient(mPackageName,
+                    SERVERLESS_CLIENT_ID);
+            mServerlessRemoteTaskClientSet = true;
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "failed to call addServerlessRemoteTaskClient, maybe the test pkg is "
+                    + "already a serverless remote task client?", e);
+        }
+
+        assertWithMessage(
+                "This test requires the test package to be a serverless remote task client").that(
+                isTestPkgServerlessClient()).isTrue();
+    }
+
+    private void assumeTaskSchedulingSupported() {
+        assumeTrue("Task scheduling is not supported, skipping the test",
+                mCarRemoteAccessManager.isTaskScheduleSupported());
+    }
 }