Add tests to verify job scheduling on events

... of rebootless APEX and preload update installation.

Changes made to the actual service:
 * DO_BINARY_MEASUREMENTS_JOB_ID is now a constant, like most of jobs in
   the framework. This makes it easy to use from the host.
 * Replace the local state of sScheduled with a query to JobScheduler,
   which is the source of truth. I've messed up the state during test
   and manual interaction, and this is supposed to make it more
   resilient.

Bug: 265244016
Test: atest BinaryTransparencyHostTest
Test: atest BinaryTransparencyServiceTest
Change-Id: I17872bbded0b5c5c44e6fbd1c669f1eb00f3333e
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index 142e3dd..dc6bdff 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -35,6 +35,7 @@
     data: [
         ":BinaryTransparencyTestApp",
         ":EasterEgg",
+        ":com.android.apex.cts.shim.v2_rebootless_prebuilt",
     ],
     test_suites: [
         "general-tests",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 84bed92..8db3d00 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -17,6 +17,7 @@
 package android.transparency.test;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,14 +30,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 // TODO: Add @Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
     private static final String PACKAGE_NAME = "android.transparency.test.app";
 
+    private static final String JOB_ID = "1740526926";
+
+    /** Waiting time for the job to be scheduled */
+    private static final int JOB_CREATION_MAX_SECONDS = 5;
+
     @After
     public void tearDown() throws Exception {
         uninstallPackage("com.android.egg");
+        uninstallRebootlessApex();
     }
 
     @Test
@@ -64,6 +73,28 @@
     }
 
     @Test
+    public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
+        cancelPendingJob();
+        installRebootlessApex();
+
+        // Verify
+        expectJobToBeScheduled();
+        // Just cancel since we can't verifying very meaningfully.
+        cancelPendingJob();
+    }
+
+    @Test
+    public void testPreloadUpdateTriggersJobScheduling() throws Exception {
+        cancelPendingJob();
+        installPackage("EasterEgg.apk");
+
+        // Verify
+        expectJobToBeScheduled();
+        // Just cancel since we can't verifying very meaningfully.
+        cancelPendingJob();
+    }
+
+    @Test
     public void testMeasureMbas() throws Exception {
         // TODO(265244016): figure out a way to install an MBA
     }
@@ -74,4 +105,47 @@
         options.setTestMethodName(method);
         runDeviceTests(options);
     }
+
+    private void cancelPendingJob() throws DeviceNotAvailableException {
+        CommandResult result = getDevice().executeShellV2Command(
+                "cmd jobscheduler cancel android " + JOB_ID);
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+    }
+
+    private void expectJobToBeScheduled() throws Exception {
+        for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) {
+            CommandResult result = getDevice().executeShellV2Command(
+                    "cmd jobscheduler get-job-state android " + JOB_ID);
+            String state = result.getStdout().toString();
+            if (state.startsWith("unknown")) {
+                // The job hasn't been scheduled yet. So try again.
+                TimeUnit.SECONDS.sleep(1);
+            } else if (result.getExitCode() != 0) {
+                fail("Failing due to unexpected job state: " + result);
+            } else {
+                // The job exists, which is all we care about here
+                return;
+            }
+        }
+        fail("Timed out waiting for the job to be scheduled");
+    }
+
+    private void installRebootlessApex() throws Exception {
+        installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
+    }
+
+    private void uninstallRebootlessApex() throws DeviceNotAvailableException {
+        // Reboot only if the APEX is not the pre-install one.
+        CommandResult result = getDevice().executeShellV2Command(
+                "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+        if (result.getStdout().contains("/data/apex/active/")) {
+            uninstallPackage("com.android.apex.cts.shim");
+            getDevice().reboot();
+
+            // Reboot enforces SELinux. Make it permissive again.
+            CommandResult runResult = getDevice().executeShellV2Command("setenforce 0");
+            assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
+        }
+    }
 }