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/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6fdbc0d..b9282763 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -96,7 +96,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@@ -287,7 +286,7 @@
* - dynamically installed mobile bundled apps (MBAs) (new in Android U)
*/
public void recordMeasurementsForAllPackages() {
- // check if we should record the resulting measurements
+ // check if we should measure and record
long currentTimeMs = System.currentTimeMillis();
if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
@@ -1227,10 +1226,8 @@
* JobService to measure all covered binaries and record result to Westworld.
*/
public static class UpdateMeasurementsJobService extends JobService {
- private static AtomicBoolean sScheduled = new AtomicBoolean();
private static long sTimeLastRanMs = 0;
- private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
- UpdateMeasurementsJobService.class.hashCode();
+ private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;
@Override
public boolean onStartJob(JobParameters params) {
@@ -1253,7 +1250,6 @@
return;
}
sTimeLastRanMs = System.currentTimeMillis();
- sScheduled.set(false);
jobFinished(params, false);
}).start();
@@ -1274,7 +1270,7 @@
return;
}
- if (sScheduled.get()) {
+ if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
Slog.d(TAG, "A measurement job has already been scheduled.");
return;
}
@@ -1300,7 +1296,6 @@
Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
- sScheduled.set(true);
Slog.d(TAG, TextUtils.formatSimple(
"Job %d to measure binaries was scheduled successfully.",
DO_BINARY_MEASUREMENTS_JOB_ID));
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);
+ }
+ }
}