Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef7eef60 authored by Christopher Tate's avatar Christopher Tate
Browse files

Fix job persistence & re-inflation

We were persisting jobs' battery-not-low constraints but were not
properly restoring that constraint when the job was inflated at boot.
This could result in a runtime bootloop (!) if the job had no other
constraints, requiring a factory reset to restore the device to
usability.

We now:

* properly inflate the battery-not-low constraint;
* persist & inflate the storage-not-low constraint, which previously was
being stripped entirely and could result in a similar crash-at-boot;
* ignore the job rather than crash the system if one is inflated into
a non-viable state; and
* formally test previously-untested constraint persistence

Bug: 130012063
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
Test: atest CtsJobSchedulerTestCases
Test: JobStoreTest with forced throw in JobInfo.Builder#build()
Change-Id: Ia3ab1eb16aeaa85336409368b4340622cec19f4c
Merged-In: Ia3ab1eb16aeaa85336409368b4340622cec19f4c
parent dec37962
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1592,5 +1592,15 @@ public class JobInfo implements Parcelable {
            }
            return new JobInfo(this);
        }

        /**
         * @hide
         */
        public String summarize() {
            final String service = (mJobService != null)
                    ? mJobService.flattenToShortString()
                    : "null";
            return "JobInfo.Builder{job:" + mJobId + "/" + service + "}";
        }
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -492,6 +492,9 @@ public final class JobStore {
            if (jobStatus.hasBatteryNotLowConstraint()) {
                out.attribute(null, "battery-not-low", Boolean.toString(true));
            }
            if (jobStatus.hasStorageNotLowConstraint()) {
                out.attribute(null, "storage-not-low", Boolean.toString(true));
            }
            out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
        }

@@ -852,6 +855,15 @@ public final class JobStore {
            jobBuilder.setExtras(extras);
            parser.nextTag(); // Consume </extras>

            final JobInfo builtJob;
            try {
                builtJob = jobBuilder.build();
            } catch (Exception e) {
                Slog.w(TAG, "Unable to build job from XML, ignoring: "
                        + jobBuilder.summarize());
                return null;
            }

            // Migrate sync jobs forward from earlier, incomplete representation
            if ("android".equals(sourcePackageName)
                    && extras != null
@@ -935,6 +947,14 @@ public final class JobStore {
            if (val != null) {
                jobBuilder.setRequiresCharging(true);
            }
            val = parser.getAttributeValue(null, "battery-not-low");
            if (val != null) {
                jobBuilder.setRequiresBatteryNotLow(true);
            }
            val = parser.getAttributeValue(null, "storage-not-low");
            if (val != null) {
                jobBuilder.setRequiresStorageNotLow(true);
            }
        }

        /**
+76 −0
Original line number Diff line number Diff line
@@ -382,6 +382,82 @@ public class JobStoreTest {
                .build());
    }

    @Test
    public void testPersistedIdleConstraint() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresDeviceIdle(true)
                .setPersisted(true);
        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);

        mTaskStoreUnderTest.add(taskStatus);
        waitForPendingIo();

        final JobSet jobStatusSet = new JobSet();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
        assertEquals("Idle constraint not persisted correctly.",
                loaded.getJob().isRequireDeviceIdle(),
                taskStatus.getJob().isRequireDeviceIdle());
    }

    @Test
    public void testPersistedChargingConstraint() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresCharging(true)
                .setPersisted(true);
        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);

        mTaskStoreUnderTest.add(taskStatus);
        waitForPendingIo();

        final JobSet jobStatusSet = new JobSet();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
        assertEquals("Charging constraint not persisted correctly.",
                loaded.getJob().isRequireCharging(),
                taskStatus.getJob().isRequireCharging());
    }

    @Test
    public void testPersistedStorageNotLowConstraint() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresStorageNotLow(true)
                .setPersisted(true);
        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);

        mTaskStoreUnderTest.add(taskStatus);
        waitForPendingIo();

        final JobSet jobStatusSet = new JobSet();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
        assertEquals("Storage-not-low constraint not persisted correctly.",
                loaded.getJob().isRequireStorageNotLow(),
                taskStatus.getJob().isRequireStorageNotLow());
    }

    @Test
    public void testPersistedBatteryNotLowConstraint() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresBatteryNotLow(true)
                .setPersisted(true);
        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);

        mTaskStoreUnderTest.add(taskStatus);
        waitForPendingIo();

        final JobSet jobStatusSet = new JobSet();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
        assertEquals("Battery-not-low constraint not persisted correctly.",
                loaded.getJob().isRequireBatteryNotLow(),
                taskStatus.getJob().isRequireBatteryNotLow());
    }

    /**
     * Helper function to kick a {@link JobInfo} through a persistence cycle and
     * assert that it's unchanged.