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

Commit 8e64e2e6 authored by Shreyas Basarge's avatar Shreyas Basarge
Browse files

Fixes JobScheduler not persisting flex and sourcePackage

There was a bug with persisteing and restoring flex and
sourcePackage. Fixed it and added tests.

Change-Id: Ie8e4714b4727ecef4254773fd4339b28f4a47c01
parent 46d53771
Loading
Loading
Loading
Loading
+1 −4
Original line number Diff line number Diff line
@@ -231,10 +231,7 @@ public class JobSchedulerService extends com.android.server.SystemService
    }

    public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId) {
        JobStatus jobStatus = new JobStatus(job, uId);
        if (packageName != null) {
            jobStatus.setSource(packageName, userId);
        }
        JobStatus jobStatus = new JobStatus(job, uId, packageName, userId);
        try {
            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+6 −9
Original line number Diff line number Diff line
@@ -282,8 +282,7 @@ public class JobStore {
                        continue;
                    }

                    JobStatus copy = new JobStatus(jobStatus.getJob(), jobStatus.getUid(),
                            jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed());
                    JobStatus copy = new JobStatus(jobStatus);
                    mStoreCopy.add(copy);
                }
            }
@@ -544,7 +543,7 @@ public class JobStore {
        private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
                IOException {
            JobInfo.Builder jobBuilder;
            int uid, userId;
            int uid, sourceUserId;

            // Read out job identifier attributes and priority.
            try {
@@ -557,7 +556,7 @@ public class JobStore {
                    jobBuilder.setPriority(Integer.valueOf(val));
                }
                val = parser.getAttributeValue(null, "sourceUserId");
                userId = val == null ? -1 : Integer.valueOf(val);
                sourceUserId = val == null ? -1 : Integer.valueOf(val);
            } catch (NumberFormatException e) {
                Slog.e(TAG, "Error parsing job's required fields, skipping");
                return null;
@@ -608,9 +607,9 @@ public class JobStore {
                try {
                    String val = parser.getAttributeValue(null, "period");
                    final long periodMillis = Long.valueOf(val);
                    jobBuilder.setPeriodic(periodMillis);
                    val = parser.getAttributeValue(null, "flex");
                    final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
                    jobBuilder.setPeriodic(periodMillis, flexMillis);
                    // As a sanity check, cap the recreated run time to be no later than flex+period
                    // from now. This is the latest the periodic could be pushed out. This could
                    // happen if the periodic ran early (at flex time before period), and then the
@@ -679,10 +678,8 @@ public class JobStore {
            parser.nextTag(); // Consume </extras>

            JobStatus js = new JobStatus(
                    jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
            if (userId != -1) {
                js.setSource(sourcePackageName, userId);
            }
                    jobBuilder.build(), uid, sourcePackageName, sourceUserId, elapsedRuntimes.first,
                    elapsedRuntimes.second);
            return js;
        }

+50 −45
Original line number Diff line number Diff line
@@ -48,13 +48,13 @@ public class JobStatus {

    final JobInfo job;
    /** Uid of the package requesting this job. */
    final int uId;
    final int callingUid;
    final String name;
    final String tag;

    String sourcePackageName;
    int sourceUserId = -1;
    int sourceUid = -1;
    final String sourcePackageName;
    final int sourceUserId;
    final int sourceUid;

    // Constraints.
    final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
@@ -91,30 +91,55 @@ public class JobStatus {

    /** Provide a handle to the service that this job will be run on. */
    public int getServiceToken() {
        return uId;
        return callingUid;
    }

    private JobStatus(JobInfo job, int uId, int numFailures) {
    private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
                      int numFailures) {
        this.job = job;
        this.uId = uId;
        this.sourceUid = uId;
        this.callingUid = callingUid;
        this.name = job.getService().flattenToShortString();
        this.tag = "*job*/" + this.name;
        this.numFailures = numFailures;

        int tempSourceUid = -1;
        if (sourceUserId != -1 && sourcePackageName != null) {
            try {
                tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
                        sourceUserId);
            } catch (RemoteException ex) {
                // Can't happen, PackageManager runs in the same process.
            }
        }
        if (tempSourceUid == -1) {
            this.sourceUid = callingUid;
            this.sourceUserId = UserHandle.getUserId(callingUid);
            this.sourcePackageName = job.getService().getPackageName();
        } else {
            this.sourceUid = tempSourceUid;
            this.sourceUserId = sourceUserId;
            this.sourcePackageName = sourcePackageName;
        }
    }

    /** Copy constructor. */
    public JobStatus(JobStatus jobStatus) {
        this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getNumFailures());
        this.sourceUserId = jobStatus.sourceUserId;
        this.sourcePackageName = jobStatus.sourcePackageName;
        this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(),
                jobStatus.getSourceUserId(), jobStatus.getNumFailures());
        this.earliestRunTimeElapsedMillis = jobStatus.getEarliestRunTime();
        this.latestRunTimeElapsedMillis = jobStatus.getLatestRunTimeElapsed();
    }

    /** Create a newly scheduled job. */
    public JobStatus(JobInfo job, int uId) {
        this(job, uId, 0);
    /**
     * Create a newly scheduled job.
     * @param callingUid Uid of the package that scheduled this job.
     * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
     *                          the calling package is the source.
     * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
     *                     calling userId.
     */
    public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId) {
        this(job, callingUid, sourcePackageName, sourceUserId, 0);

        final long elapsedNow = SystemClock.elapsedRealtime();

@@ -136,9 +161,9 @@ public class JobStatus {
     * wallclock runtime rather than resetting it on every boot.
     * We consider a freshly loaded job to no longer be in back-off.
     */
    public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
                      long latestRunTimeElapsedMillis) {
        this(job, uId, 0);
    public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
                     long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
        this(job, callingUid, sourcePackageName, sourceUserId, 0);

        this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
        this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
@@ -147,9 +172,8 @@ public class JobStatus {
    /** Create a new job to be rescheduled with the provided parameters. */
    public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
                      long newLatestRuntimeElapsedMillis, int backoffAttempt) {
        this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
        this.sourceUserId = rescheduling.sourceUserId;
        this.sourcePackageName = rescheduling.sourcePackageName;
        this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(),
                rescheduling.getSourceUserId(), backoffAttempt);

        earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
        latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
@@ -172,7 +196,7 @@ public class JobStatus {
    }

    public String getSourcePackageName() {
        return sourcePackageName != null ? sourcePackageName : job.getService().getPackageName();
        return sourcePackageName;
    }

    public int getSourceUid() {
@@ -180,18 +204,15 @@ public class JobStatus {
    }

    public int getSourceUserId() {
        if (sourceUserId == -1) {
            sourceUserId = getUserId();
        }
        return sourceUserId;
    }

    public int getUserId() {
        return UserHandle.getUserId(uId);
        return UserHandle.getUserId(callingUid);
    }

    public int getUid() {
        return uId;
        return callingUid;
    }

    public String getName() {
@@ -278,7 +299,7 @@ public class JobStatus {
    }

    public boolean matches(int uid, int jobId) {
        return this.job.getId() == jobId && this.uId == uid;
        return this.job.getId() == jobId && this.callingUid == uid;
    }

    @Override
@@ -312,22 +333,6 @@ public class JobStatus {
        }
    }

    public void setSource(String sourcePackageName, int sourceUserId) {
        this.sourcePackageName = sourcePackageName;
        this.sourceUserId = sourceUserId;
        try {
            sourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
                    sourceUserId);
        } catch (RemoteException ex) {
            // Can't happen, PackageManager runs in the same process.
        }
        if (sourceUid == -1) {
            sourceUid = uId;
            this.sourceUserId = getUserId();
            this.sourcePackageName = null;
        }
    }

    /**
     * Convenience function to identify a job uniquely without pulling all the data that
     * {@link #toString()} returns.
@@ -338,7 +343,7 @@ public class JobStatus {
        sb.append(" jId=");
        sb.append(job.getId());
        sb.append(" uid=");
        UserHandle.formatUid(sb, uId);
        UserHandle.formatUid(sb, callingUid);
        sb.append(' ');
        sb.append(job.getService().flattenToShortString());
        return sb.toString();
@@ -346,7 +351,7 @@ public class JobStatus {

    // Dumpsys infrastructure
    public void dump(PrintWriter pw, String prefix) {
        pw.print(prefix); UserHandle.formatUid(pw, uId);
        pw.print(prefix); UserHandle.formatUid(pw, callingUid);
        pw.print(" tag="); pw.println(tag);
        pw.print(prefix);
        pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
+57 −15
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ public class JobStoreTest extends AndroidTestCase {
                .setMinimumLatency(runFromMillis)
                .setPersisted(true)
                .build();
        final JobStatus ts = new JobStatus(task, SOME_UID);
        final JobStatus ts = new JobStatus(task, SOME_UID, null, -1);
        mTaskStoreUnderTest.add(ts);
        Thread.sleep(IO_WAIT);
        // Manually load tasks from xml file.
@@ -91,8 +91,8 @@ public class JobStoreTest extends AndroidTestCase {
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                .setPersisted(true)
                .build();
        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, null, -1);
        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, null, -1);
        mTaskStoreUnderTest.add(taskStatus1);
        mTaskStoreUnderTest.add(taskStatus2);
        Thread.sleep(IO_WAIT);
@@ -140,7 +140,7 @@ public class JobStoreTest extends AndroidTestCase {
        extras.putInt("into", 3);
        b.setExtras(extras);
        final JobInfo task = b.build();
        JobStatus taskStatus = new JobStatus(task, SOME_UID);
        JobStatus taskStatus = new JobStatus(task, SOME_UID, null, -1);

        mTaskStoreUnderTest.add(taskStatus);
        Thread.sleep(IO_WAIT);
@@ -151,17 +151,59 @@ public class JobStoreTest extends AndroidTestCase {
        JobStatus loaded = jobStatusSet.iterator().next();
        assertTasksEqual(task, loaded.getJob());
    }
    public void testWritingTaskWithSourcePackage() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresDeviceIdle(true)
                .setPeriodic(10000L)
                .setRequiresCharging(true)
                .setPersisted(true);
        JobStatus taskStatus = new JobStatus(b.build(), SOME_UID, "com.google.android.gms", 0);

        mTaskStoreUnderTest.add(taskStatus);
        Thread.sleep(IO_WAIT);

        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.iterator().next();
        assertEquals("Source package not equal.", loaded.getSourcePackageName(),
                taskStatus.getSourcePackageName());
        assertEquals("Source user not equal.", loaded.getSourceUserId(),
                taskStatus.getSourceUserId());
    }

    public void testWritingTaskWithFlex() throws Exception {
        JobInfo.Builder b = new Builder(8, mComponent)
                .setRequiresDeviceIdle(true)
                .setPeriodic(5*60*60*1000, 1*60*60*1000)
                .setRequiresCharging(true)
                .setPersisted(true);
        JobStatus taskStatus = new JobStatus(b.build(), SOME_UID, null, -1);

        mTaskStoreUnderTest.add(taskStatus);
        Thread.sleep(IO_WAIT);

        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
        JobStatus loaded = jobStatusSet.iterator().next();
        assertEquals("Period not equal.", loaded.getJob().getIntervalMillis(),
                taskStatus.getJob().getIntervalMillis());
        assertEquals("Flex not equal.", loaded.getJob().getFlexMillis(),
                taskStatus.getJob().getFlexMillis());
    }

    public void testMassivePeriodClampedOnRead() throws Exception {
        final long TEN_SECONDS = 10000L;
        final long ONE_HOUR = 60*60*1000L; // flex
        final long TWO_HOURS = 2 * ONE_HOUR; // period
        JobInfo.Builder b = new Builder(8, mComponent)
                .setPeriodic(TEN_SECONDS)
                .setPeriodic(TWO_HOURS, ONE_HOUR)
                .setPersisted(true);
        final long invalidLateRuntimeElapsedMillis =
                SystemClock.elapsedRealtime() + (TEN_SECONDS * 2) + 5000;  // >2P from now.
                SystemClock.elapsedRealtime() + (TWO_HOURS * ONE_HOUR) + TWO_HOURS;  // > period+flex
        final long invalidEarlyRuntimeElapsedMillis =
                invalidLateRuntimeElapsedMillis - TEN_SECONDS;  // Early is (late - period).
        final JobStatus js = new JobStatus(b.build(), SOME_UID,
                invalidLateRuntimeElapsedMillis - TWO_HOURS;  // Early is (late - period).
        final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */,
                invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis);

        mTaskStoreUnderTest.add(js);
@@ -176,10 +218,10 @@ public class JobStoreTest extends AndroidTestCase {
        // call SystemClock.elapsedRealtime after doing the disk i/o.
        final long newNowElapsed = SystemClock.elapsedRealtime();
        assertTrue("Early runtime wasn't correctly clamped.",
                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
        // Assert late runtime was clamped to be now + period*2.
                loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS);
        // Assert late runtime was clamped to be now + period + flex.
        assertTrue("Early runtime wasn't correctly clamped.",
                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS * 2);
                loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS + ONE_HOUR);
    }

    public void testPriorityPersisted() throws Exception {
@@ -187,7 +229,7 @@ public class JobStoreTest extends AndroidTestCase {
                .setOverrideDeadline(5000)
                .setPriority(42)
                .setPersisted(true);
        final JobStatus js = new JobStatus(b.build(), SOME_UID);
        final JobStatus js = new JobStatus(b.build(), SOME_UID, null, -1);
        mTaskStoreUnderTest.add(js);
        Thread.sleep(IO_WAIT);
        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
@@ -203,12 +245,12 @@ public class JobStoreTest extends AndroidTestCase {
        JobInfo.Builder b = new Builder(42, mComponent)
                .setOverrideDeadline(10000)
                .setPersisted(false);
        JobStatus jsNonPersisted = new JobStatus(b.build(), SOME_UID);
        JobStatus jsNonPersisted = new JobStatus(b.build(), SOME_UID, null, -1);
        mTaskStoreUnderTest.add(jsNonPersisted);
        b = new Builder(43, mComponent)
                .setOverrideDeadline(10000)
                .setPersisted(true);
        JobStatus jsPersisted = new JobStatus(b.build(), SOME_UID);
        JobStatus jsPersisted = new JobStatus(b.build(), SOME_UID, null, -1);
        mTaskStoreUnderTest.add(jsPersisted);
        Thread.sleep(IO_WAIT);
        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();