1 /*
2  * Copyright (C) 2023 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.cts;
18 
19 import static android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS;
20 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_CUSTOM;
21 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_ENTER_GARAGE_MODE;
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.junit.Assume.assumeFalse;
28 import static org.junit.Assume.assumeNoException;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.car.Car;
32 import android.car.feature.Flags;
33 import android.car.remoteaccess.CarRemoteAccessManager;
34 import android.car.remoteaccess.CarRemoteAccessManager.CompletableRemoteTaskFuture;
35 import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler;
36 import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskSchedulerException;
37 import android.car.remoteaccess.CarRemoteAccessManager.RemoteTaskClientCallback;
38 import android.car.remoteaccess.CarRemoteAccessManager.ScheduleInfo;
39 import android.car.remoteaccess.RemoteTaskClientRegistrationInfo;
40 import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
41 import android.platform.test.annotations.AppModeFull;
42 import android.platform.test.annotations.RequiresFlagsEnabled;
43 import android.util.Log;
44 
45 import androidx.annotation.NonNull;
46 import androidx.annotation.Nullable;
47 import androidx.test.platform.app.InstrumentationRegistry;
48 
49 import com.android.car.remoteaccess.CarRemoteAccessDumpProto;
50 import com.android.car.remoteaccess.CarRemoteAccessDumpProto.ServerlessClientInfo;
51 import com.android.compatibility.common.util.ApiTest;
52 import com.android.compatibility.common.util.PollingCheck;
53 import com.android.compatibility.common.util.ProtoUtils;
54 import com.android.internal.annotations.GuardedBy;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Test;
59 
60 import java.time.Duration;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.concurrent.Executor;
64 
65 @AppModeFull(reason = "Instant Apps cannot get car related permissions")
66 public final class CarRemoteAccessManagerTest extends AbstractCarTestCase {
67 
68     private static final String TAG = CarRemoteAccessManagerTest.class.getSimpleName();
69     private static final int CALLBACK_WAIT_TIME_MS = 2_000;
70     private static final String INVALID_TASK_ID = "THIS_ID_CANNOT_BE_VALID_!@#$%^&*()";
71     private static final String DUMP_COMMAND =
72             "dumpsys car_service --services CarRemoteAccessService --proto";
73     private static final String SERVERLESS_CLIENT_ID = "TestServerlessClientId";
74     private static final String TEST_SCHEDULE_ID_1 = "TestScheduleId1";
75     private static final String TEST_SCHEDULE_ID_2 = "TestScheduleId2";
76     private static final byte[] TEST_TASK_DATA = "test data".getBytes();
77 
78     private Executor mExecutor;
79     private CarRemoteAccessManager mCarRemoteAccessManager;
80     private boolean mServerlessRemoteTaskClientSet = false;
81     private String mPackageName;
82 
83     @Before
setUp()84     public void setUp() throws Exception {
85         assumeTrue("CarRemoteAccessService is not enabled, skipping test",
86                 getCar().isFeatureEnabled(Car.CAR_REMOTE_ACCESS_SERVICE));
87 
88         mExecutor = mContext.getMainExecutor();
89         mCarRemoteAccessManager = (CarRemoteAccessManager) getCar()
90                 .getCarManager(Car.CAR_REMOTE_ACCESS_SERVICE);
91         assertThat(mCarRemoteAccessManager).isNotNull();
92         mPackageName = mContext.getPackageName();
93     }
94 
95     @After
tearDown()96     public void tearDown() throws Exception {
97         if (mServerlessRemoteTaskClientSet) {
98             mCarRemoteAccessManager.removeServerlessRemoteTaskClient(mPackageName);
99         }
100     }
101 
102     @Test
103     @ApiTest(apis = {
104             "android.car.remoteaccess.CarRemoteAccessManager#setRemoteTaskClient",
105     })
106     @RequiresFlagsEnabled(Flags.FLAG_CAR_DUMP_TO_PROTO)
testSetRemoteTaskClient_regularClient()107     public void testSetRemoteTaskClient_regularClient() throws Exception {
108         assumeFalse("This test requires the test package to be a regular remote task client",
109                 isTestPkgServerlessClient());
110 
111         RemoteTaskClientCallbackImpl callback = new RemoteTaskClientCallbackImpl();
112 
113         mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callback);
114 
115         PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS, () -> callback.getServiceId() != null);
116         PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS, () -> callback.getVehicleId() != null);
117         PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS, () -> callback.getProcessorId() != null);
118         PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS, () -> callback.getClientId() != null);
119     }
120 
121     @Test
122     @ApiTest(apis = {
123             "android.car.remoteaccess.CarRemoteAccessManager#setRemoteTaskClient"
124     })
125     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
126     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testSetRemoteTaskClient_serverlessClient()127     public void testSetRemoteTaskClient_serverlessClient() throws Exception {
128         setSelfAsServerlessClient();
129 
130         RemoteTaskClientCallbackImpl callback = new RemoteTaskClientCallbackImpl();
131 
132         mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callback);
133 
134         PollingCheck.waitFor(CALLBACK_WAIT_TIME_MS,
135                 () -> callback.isServerlessClientRegistered());
136     }
137 
138     /**
139      * Tests that calling {@code setRemoteTaskClient} twice from the same client is not allowed.
140      */
141     @Test
142     @ApiTest(apis = {
143             "android.car.remoteaccess.CarRemoteAccessManager#setRemoteTaskClient"
144     })
testSetRemoteTaskClient_withAlreadyRegisteredClient()145     public void testSetRemoteTaskClient_withAlreadyRegisteredClient() {
146         RemoteTaskClientCallbackImpl callbackOne = new RemoteTaskClientCallbackImpl();
147         RemoteTaskClientCallbackImpl callbackTwo = new RemoteTaskClientCallbackImpl();
148 
149         mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callbackOne);
150 
151         assertThrows(IllegalStateException.class,
152                 () -> mCarRemoteAccessManager.setRemoteTaskClient(mExecutor,
153                         callbackTwo));
154     }
155 
156     @Test
157     @ApiTest(apis = {
158             "android.car.remoteaccess.CarRemoteAccessManager#setRemoteTaskClient",
159             "android.car.remoteaccess.CarRemoteAccessManager#clearRemoteTaskClient"
160     })
testClearRemoteTaskClient()161     public void testClearRemoteTaskClient() {
162         RemoteTaskClientCallbackImpl callback = new RemoteTaskClientCallbackImpl();
163         mCarRemoteAccessManager.setRemoteTaskClient(mExecutor, callback);
164 
165         mCarRemoteAccessManager.clearRemoteTaskClient();
166 
167         // Calling clearRemoteTaskClient again to ensure that multiple calls do not cause errors.
168         mCarRemoteAccessManager.clearRemoteTaskClient();
169     }
170 
171     @Test
172     @ApiTest(apis = {"android.car.remoteaccess.CarRemoteAccessManager#clearRemoteTaskClient"})
testClearRemoteTaskClient_unregisteredClient()173     public void testClearRemoteTaskClient_unregisteredClient() {
174         mCarRemoteAccessManager.clearRemoteTaskClient();
175     }
176 
177     @Test
178     @ApiTest(apis = {
179             "android.car.remoteaccess.CarRemoteAccessManager#reportRemoteTaskDone"
180     })
testReportRemoteTaskDone_unregisteredClient()181     public void testReportRemoteTaskDone_unregisteredClient() {
182         assertThrows(IllegalStateException.class,
183                 () -> mCarRemoteAccessManager.reportRemoteTaskDone(INVALID_TASK_ID));
184     }
185 
186     @Test
187     @ApiTest(apis = {
188             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
189             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
190     })
191     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
192     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testGetInVehicleTaskScheduler_notSupported()193     public void testGetInVehicleTaskScheduler_notSupported() {
194         setSelfAsServerlessClient();
195 
196         assumeFalse("Task scheduling is supported, skipping the test",
197                 mCarRemoteAccessManager.isTaskScheduleSupported());
198 
199         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
200         assertWithMessage("InVehicleTaskScheduler must be null when task schedule is not supported")
201                 .that(taskScheduler).isNull();
202     }
203 
204     @Test
205     @ApiTest(apis = {
206             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
207             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
208     })
209     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
210     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testGetInVehicleTaskScheduler_isSupported()211     public void testGetInVehicleTaskScheduler_isSupported() {
212         setSelfAsServerlessClient();
213         assumeTaskSchedulingSupported();
214 
215         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
216         assertWithMessage("InVehicleTaskScheduler must not be null when task schedule is supported")
217                 .that(taskScheduler).isNotNull();
218     }
219 
220     @Test
221     @ApiTest(apis = {
222             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
223             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
224             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
225                     + "getSupportedTaskTypes",
226     })
227     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
228     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testGetSupportedTaskTypes_TaskTypeMustIncludeCustom()229     public void testGetSupportedTaskTypes_TaskTypeMustIncludeCustom() throws Exception {
230         setSelfAsServerlessClient();
231         assumeTaskSchedulingSupported();
232 
233         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
234         int[] supportedTaskTypes = new int[0];
235         try {
236             supportedTaskTypes = taskScheduler.getSupportedTaskTypes();
237         } catch (InVehicleTaskSchedulerException e) {
238             assumeNoException("Assume getInVehicleTaskScheduler to succeed", e);
239         }
240 
241         assertWithMessage("Supported task types must contain TASK_TYPE_CUSTOM")
242                 .that(supportedTaskTypes).asList().contains(TASK_TYPE_CUSTOM);
243     }
244 
245     @Test
246     @ApiTest(apis = {
247             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
248             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
249             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
250             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
251                     + "unscheduleAllTasks",
252     })
253     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
254     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testScheduleTask_TaskTypeCustom()255     public void testScheduleTask_TaskTypeCustom() throws Exception {
256         setSelfAsServerlessClient();
257         assumeTaskSchedulingSupported();
258 
259         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
260         // Schedule the task to be executed 30s later.
261         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
262         ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
263                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
264                 .setTaskData(TEST_TASK_DATA).build();
265         try {
266             taskScheduler.scheduleTask(scheduleInfo);
267         } catch (InVehicleTaskSchedulerException e) {
268             assumeNoException("Assume task schedule to succeed", e);
269         }
270 
271         taskScheduler.unscheduleAllTasks();
272     }
273 
274     @Test
275     @ApiTest(apis = {
276             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
277             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
278             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
279             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
280                     + "getSupportedTaskTypes",
281             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
282                     + "isTaskScheduled",
283             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
284                     + "getAllPendingScheduledTasks",
285             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
286                     + "unscheduleAllTasks",
287     })
288     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
289     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testScheduleTask_TaskTypeEnterGarageMode()290     public void testScheduleTask_TaskTypeEnterGarageMode() throws Exception {
291         setSelfAsServerlessClient();
292         assumeTaskSchedulingSupported();
293 
294         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
295         int[] supportedTaskTypes = taskScheduler.getSupportedTaskTypes();
296         boolean supportTaskTypeEnterGarageMode = false;
297         for (int i = 0; i < supportedTaskTypes.length; i++) {
298             if (supportedTaskTypes[i] == TASK_TYPE_ENTER_GARAGE_MODE) {
299                 supportTaskTypeEnterGarageMode = true;
300             }
301         }
302 
303         assumeTrue("Task type: ENTER_GARAGE_MODE is not supported, skip the test",
304                 supportTaskTypeEnterGarageMode);
305 
306         // Schedule the task to be executed 30s later.
307         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
308         ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
309                 TASK_TYPE_ENTER_GARAGE_MODE, startTimeInEpochSeconds).build();
310         try {
311             taskScheduler.scheduleTask(scheduleInfo);
312         } catch (InVehicleTaskSchedulerException e) {
313             assumeNoException("Assume task schedule to succeed", e);
314         }
315 
316         try {
317             expectWithMessage("isTaskScheduled for scheduled task").that(
318                     taskScheduler.isTaskScheduled(TEST_SCHEDULE_ID_1)).isTrue();
319 
320             List<ScheduleInfo> gotScheduleInfo = taskScheduler.getAllPendingScheduledTasks();
321 
322             assertWithMessage("Must return one scheduled tasks").that(gotScheduleInfo).hasSize(1);
323             ScheduleInfo info = gotScheduleInfo.get(0);
324             expectWithMessage("Got expected scheduleId").that(info.getScheduleId())
325                     .isEqualTo(TEST_SCHEDULE_ID_1);
326             expectWithMessage("Got expected task data").that(info.getTaskType()).isEqualTo(
327                     TASK_TYPE_ENTER_GARAGE_MODE);
328             expectWithMessage("Got expected task data").that(info.getTaskData()).isEmpty();
329             expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(1);
330             expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
331                     Duration.ZERO);
332         } finally {
333             taskScheduler.unscheduleAllTasks();
334         }
335     }
336 
337     @Test
338     @ApiTest(apis = {
339             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
340             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
341             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
342             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
343                     + "unscheduleAllTasks",
344     })
345     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
346     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testScheduleTask_duplicateScheduleIdMustThrowException()347     public void testScheduleTask_duplicateScheduleIdMustThrowException() throws Exception {
348         setSelfAsServerlessClient();
349         assumeTaskSchedulingSupported();
350 
351         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
352         // Schedule the task to be executed 30s later.
353         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
354         ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
355                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
356                 .setTaskData(TEST_TASK_DATA).build();
357         try {
358             taskScheduler.scheduleTask(scheduleInfo);
359         } catch (InVehicleTaskSchedulerException e) {
360             assumeNoException("Assume task schedule to succeed", e);
361         }
362 
363         try {
364             // Schedule the same task twice must cause IllegalArgumentException.
365             assertThrows(IllegalArgumentException.class, () -> taskScheduler.scheduleTask(
366                     scheduleInfo));
367         } finally {
368             taskScheduler.unscheduleAllTasks();
369         }
370     }
371 
372     @Test
373     @ApiTest(apis = {
374             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
375             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
376             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
377             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
378                     + "unscheduleAllTasks",
379             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
380                     + "isTaskScheduled",
381     })
382     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
383     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testIsTaskScheduled()384     public void testIsTaskScheduled() throws Exception {
385         setSelfAsServerlessClient();
386         assumeTaskSchedulingSupported();
387 
388         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
389         // Schedule the task to be executed 30s later.
390         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
391         ScheduleInfo scheduleInfo = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
392                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
393                 .setTaskData(TEST_TASK_DATA).build();
394         try {
395             taskScheduler.scheduleTask(scheduleInfo);
396         } catch (InVehicleTaskSchedulerException e) {
397             assumeNoException("Assume task schedule to succeed", e);
398         }
399 
400         try {
401             expectWithMessage("isTaskScheduled for scheduled task").that(
402                     taskScheduler.isTaskScheduled(TEST_SCHEDULE_ID_1)).isTrue();
403             expectWithMessage("isTaskScheduled for unscheduled task").that(
404                     taskScheduler.isTaskScheduled(TEST_SCHEDULE_ID_2)).isFalse();
405         } finally {
406             taskScheduler.unscheduleAllTasks();
407         }
408     }
409 
410     @Test
411     @ApiTest(apis = {
412             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
413             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
414             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
415             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
416                     + "unscheduleAllTasks",
417             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
418                     + "getAllPendingScheduledTasks",
419     })
420     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
421     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testGetAllPendingScheduledTasks()422     public void testGetAllPendingScheduledTasks() throws Exception {
423         setSelfAsServerlessClient();
424         assumeTaskSchedulingSupported();
425 
426         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
427         // Schedule the task to be executed 30s later.
428         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
429         ScheduleInfo scheduleInfo1 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
430                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
431                 .setTaskData(TEST_TASK_DATA).build();
432         ScheduleInfo scheduleInfo2 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_2,
433                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
434                 .setTaskData(TEST_TASK_DATA).setCount(2).setPeriodic(Duration.ofSeconds(30))
435                 .build();
436 
437         try {
438             taskScheduler.unscheduleAllTasks();
439             taskScheduler.scheduleTask(scheduleInfo1);
440             taskScheduler.scheduleTask(scheduleInfo2);
441         } catch (InVehicleTaskSchedulerException e) {
442             assumeNoException("Assume task schedule to succeed", e);
443         }
444 
445         try {
446             List<ScheduleInfo> scheduleInfo = taskScheduler.getAllPendingScheduledTasks();
447 
448             assertWithMessage("Must return two scheduled tasks").that(scheduleInfo).hasSize(2);
449             List<String> gotScheduleIds = new ArrayList<>();
450             for (int i = 0; i < scheduleInfo.size(); i++) {
451                 ScheduleInfo info = scheduleInfo.get(i);
452                 expectWithMessage("Got expected scheduleId").that(info.getScheduleId())
453                         .isAnyOf(TEST_SCHEDULE_ID_1, TEST_SCHEDULE_ID_2);
454                 gotScheduleIds.add(info.getScheduleId());
455                 expectWithMessage("Got expected task data").that(info.getTaskData()).isEqualTo(
456                         TEST_TASK_DATA);
457                 if (info.getScheduleId().equals(TEST_SCHEDULE_ID_1)) {
458                     expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(1);
459                     expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
460                             Duration.ZERO);
461                 } else {
462                     expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(2);
463                     expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
464                             Duration.ofSeconds(30));
465                 }
466             }
467             expectWithMessage("Got all expected schedule Ids").that(gotScheduleIds).containsExactly(
468                     TEST_SCHEDULE_ID_1, TEST_SCHEDULE_ID_2);
469         } finally {
470             taskScheduler.unscheduleAllTasks();
471         }
472     }
473 
474     @Test
475     @ApiTest(apis = {
476             "android.car.remoteaccess.CarRemoteAccessManager#isTaskScheduleSupported",
477             "android.car.remoteaccess.CarRemoteAccessManager#getInVehicleTaskScheduler",
478             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#scheduleTask",
479             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
480                     + "unscheduleAllTasks",
481             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
482                     + "getAllPendingScheduledTasks",
483             "android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler#"
484                     + "unscheduleTask",
485     })
486     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
487     @RequiresFlagsEnabled({Flags.FLAG_SERVERLESS_REMOTE_ACCESS, Flags.FLAG_CAR_DUMP_TO_PROTO})
testUnscheduleTask()488     public void testUnscheduleTask() throws Exception {
489         setSelfAsServerlessClient();
490         assumeTaskSchedulingSupported();
491 
492         InVehicleTaskScheduler taskScheduler = mCarRemoteAccessManager.getInVehicleTaskScheduler();
493         // Schedule the task to be executed 30s later.
494         long startTimeInEpochSeconds = System.currentTimeMillis() / 1000 + 30;
495         ScheduleInfo scheduleInfo1 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_1,
496                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
497                 .setTaskData(TEST_TASK_DATA).build();
498         ScheduleInfo scheduleInfo2 = new ScheduleInfo.Builder(TEST_SCHEDULE_ID_2,
499                 TASK_TYPE_CUSTOM, startTimeInEpochSeconds)
500                 .setTaskData(TEST_TASK_DATA).setCount(2).setPeriodic(Duration.ofSeconds(30))
501                 .build();
502 
503         try {
504             taskScheduler.unscheduleAllTasks();
505             taskScheduler.scheduleTask(scheduleInfo1);
506             taskScheduler.scheduleTask(scheduleInfo2);
507         } catch (InVehicleTaskSchedulerException e) {
508             assumeNoException("Assume task schedule to succeed", e);
509         }
510 
511         taskScheduler.unscheduleTask(TEST_SCHEDULE_ID_2);
512 
513         try {
514             List<ScheduleInfo> scheduleInfo = taskScheduler.getAllPendingScheduledTasks();
515 
516             assertWithMessage("Must return one scheduled tasks").that(scheduleInfo).hasSize(1);
517             ScheduleInfo info = scheduleInfo.get(0);
518             expectWithMessage("Got expected scheduleId").that(info.getScheduleId())
519                     .isEqualTo(TEST_SCHEDULE_ID_1);
520             expectWithMessage("Got expected task data").that(info.getTaskType()).isEqualTo(
521                     TASK_TYPE_CUSTOM);
522             expectWithMessage("Got expected task data").that(info.getTaskData()).isEqualTo(
523                     TEST_TASK_DATA);
524             expectWithMessage("Got expected count").that(info.getCount()).isEqualTo(1);
525             expectWithMessage("Got expected periodic").that(info.getPeriodic()).isEqualTo(
526                     Duration.ZERO);
527         } finally {
528             taskScheduler.unscheduleAllTasks();
529         }
530     }
531 
532     @Test
533     @ApiTest(apis = {"android.car.remoteaccess.CarRemoteAccessManager#isVehicleInUseSupported"})
534     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
535     @RequiresFlagsEnabled(Flags.FLAG_CAR_DUMP_TO_PROTO)
testVehicleInUseMustBeSupported()536     public void testVehicleInUseMustBeSupported() {
537         assertWithMessage("VHAL property: VEHICLE_IN_USE must be supported if remote access feature"
538                 + " is enabled").that(mCarRemoteAccessManager.isVehicleInUseSupported()).isTrue();
539     }
540 
541     @Test
542     @ApiTest(apis = {"android.car.remoteaccess.CarRemoteAccessManager#isShutdownRequestSupported"})
543     @EnsureHasPermission(PERMISSION_CONTROL_REMOTE_ACCESS)
544     @RequiresFlagsEnabled(Flags.FLAG_CAR_DUMP_TO_PROTO)
testShutdownRequestMustBeSupported()545     public void testShutdownRequestMustBeSupported() {
546         assertWithMessage("VHAL property: SHUTDOWN_REQUEST must be supported if remote access "
547                 + "feature is enabled").that(mCarRemoteAccessManager.isShutdownRequestSupported())
548                 .isTrue();
549     }
550 
551     private static final class RemoteTaskClientCallbackImpl implements RemoteTaskClientCallback {
552         private final Object mLock = new Object();
553 
554         @GuardedBy("mLock")
555         private RemoteTaskClientRegistrationInfo mInfo;
556         @GuardedBy("mLock")
557         private boolean mServerlessClientRegistered;
558 
559         @Override
onRegistrationUpdated(@onNull RemoteTaskClientRegistrationInfo info)560         public void onRegistrationUpdated(@NonNull RemoteTaskClientRegistrationInfo info) {
561             synchronized (mLock) {
562                 mInfo = info;
563             }
564         }
565 
566         @Override
onServerlessClientRegistered()567         public void onServerlessClientRegistered() {
568             synchronized (mLock) {
569                 mServerlessClientRegistered = true;
570             }
571         }
572 
573         @Override
onRegistrationFailed()574         public void onRegistrationFailed() {
575         }
576 
577         @Override
onRemoteTaskRequested(@onNull String taskId, @Nullable byte[] data, int taskMaxDurationInSec)578         public void onRemoteTaskRequested(@NonNull String taskId, @Nullable byte[] data,
579                 int taskMaxDurationInSec) {
580         }
581 
582         @Override
onShutdownStarting(@onNull CompletableRemoteTaskFuture future)583         public void onShutdownStarting(@NonNull CompletableRemoteTaskFuture future) {
584         }
585 
getServiceId()586         public String getServiceId() {
587             synchronized (mLock) {
588                 if (mInfo == null) {
589                     return null;
590                 }
591                 return mInfo.getVehicleId();
592             }
593         }
594 
getVehicleId()595         public String getVehicleId() {
596 
597             synchronized (mLock) {
598                 if (mInfo == null) {
599                     return null;
600                 }
601                 return mInfo.getVehicleId();
602             }
603         }
604 
getProcessorId()605         public String getProcessorId() {
606             synchronized (mLock) {
607                 if (mInfo == null) {
608                     return null;
609                 }
610                 return mInfo.getProcessorId();
611             }
612         }
613 
getClientId()614         public String getClientId() {
615             synchronized (mLock) {
616                 if (mInfo == null) {
617                     return null;
618                 }
619                 return mInfo.getClientId();
620             }
621         }
622 
isServerlessClientRegistered()623         public boolean isServerlessClientRegistered() {
624             synchronized (mLock) {
625                 return mServerlessClientRegistered;
626             }
627         }
628     }
629 
isTestPkgServerlessClient()630     private boolean isTestPkgServerlessClient() {
631         try {
632             CarRemoteAccessDumpProto dump = ProtoUtils.getProto(
633                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
634                     CarRemoteAccessDumpProto.class, DUMP_COMMAND);
635 
636             for (int i = 0; i < dump.getServerlessClientsCount(); i++) {
637                 ServerlessClientInfo serverlessClientInfo = dump.getServerlessClients(i);
638                 if (serverlessClientInfo.getPackageName().equals(mContext.getPackageName())) {
639                     return true;
640                 }
641             }
642         } catch (Exception e) {
643             Log.w(TAG, "Failed to check whether this test is a serverless remote task client"
644                     + ", default to false", e);
645         }
646 
647         return false;
648     }
649 
setSelfAsServerlessClient()650     private void setSelfAsServerlessClient() {
651         try {
652             mCarRemoteAccessManager.addServerlessRemoteTaskClient(mPackageName,
653                     SERVERLESS_CLIENT_ID);
654             mServerlessRemoteTaskClientSet = true;
655         } catch (IllegalArgumentException e) {
656             Log.w(TAG, "failed to call addServerlessRemoteTaskClient, maybe the test pkg is "
657                     + "already a serverless remote task client?", e);
658         }
659 
660         assertWithMessage(
661                 "This test requires the test package to be a serverless remote task client").that(
662                 isTestPkgServerlessClient()).isTrue();
663     }
664 
assumeTaskSchedulingSupported()665     private void assumeTaskSchedulingSupported() {
666         assumeTrue("Task scheduling is not supported, skipping the test",
667                 mCarRemoteAccessManager.isTaskScheduleSupported());
668     }
669 }
670