Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +27 −3 Original line number Diff line number Diff line Loading @@ -482,6 +482,7 @@ public class JobSchedulerService extends com.android.server.SystemService case Constants.KEY_RUNTIME_UI_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS: case Constants.KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS: case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS: if (!runtimeUpdated) { mConstants.updateRuntimeConstantsLocked(); Loading Loading @@ -582,6 +583,8 @@ public class JobSchedulerService extends com.android.server.SystemService "runtime_min_ui_data_transfer_guarantee_buffer_factor"; private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = "runtime_min_ui_data_transfer_guarantee_ms"; private static final String KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS = "runtime_cumulative_ui_limit_ms"; private static final String KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = "runtime_use_data_estimates_for_limits"; Loading Loading @@ -622,6 +625,7 @@ public class JobSchedulerService extends com.android.server.SystemService 1.35f; public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS); public static final long DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS = 24 * HOUR_IN_MILLIS; public static final boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false; static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true; static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000; Loading Loading @@ -759,6 +763,12 @@ public class JobSchedulerService extends com.android.server.SystemService public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS; /** * The maximum amount of cumulative time we will let a user-initiated job run for * before downgrading it. */ public long RUNTIME_CUMULATIVE_UI_LIMIT_MS = DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS; /** * Whether to use data estimates to determine execution limits for execution limits. */ Loading Loading @@ -881,6 +891,7 @@ public class JobSchedulerService extends com.android.server.SystemService KEY_RUNTIME_MIN_UI_GUARANTEE_MS, KEY_RUNTIME_UI_LIMIT_MS, KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS); // Make sure min runtime for regular jobs is at least 10 minutes. Loading Loading @@ -915,6 +926,11 @@ public class JobSchedulerService extends com.android.server.SystemService properties.getLong( KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS)); // The cumulative runtime limit should be at least the max execution limit. RUNTIME_CUMULATIVE_UI_LIMIT_MS = Math.max(RUNTIME_UI_LIMIT_MS, properties.getLong( KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS)); RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean( KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, Loading Loading @@ -972,6 +988,7 @@ public class JobSchedulerService extends com.android.server.SystemService RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println(); pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println(); pw.print(KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, RUNTIME_CUMULATIVE_UI_LIMIT_MS).println(); pw.print(KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).println(); Loading Loading @@ -2452,11 +2469,16 @@ public class JobSchedulerService extends com.android.server.SystemService JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(), failureToReschedule.getCumulativeExecutionTimeMs()); if (stopReason == JobParameters.STOP_REASON_USER) { // Demote all jobs to regular for user stops so they don't keep privileges. newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } if (newJob.getCumulativeExecutionTimeMs() >= mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS && newJob.shouldTreatAsUserInitiatedJob()) { newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ); } if (job.isPeriodic()) { newJob.setOriginalLatestRunTimeElapsed( failureToReschedule.getOriginalLatestRunTimeElapsed()); Loading Loading @@ -2548,7 +2570,8 @@ public class JobSchedulerService extends com.android.server.SystemService elapsedNow + period - flex, elapsedNow + period, 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); periodicToReschedule.getLastFailedRunTime(), 0 /* Reset cumulativeExecutionTime because of successful execution */); } final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed Loading @@ -2563,7 +2586,8 @@ public class JobSchedulerService extends com.android.server.SystemService newEarliestRunTimeElapsed, newLatestRuntimeElapsed, 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); periodicToReschedule.getLastFailedRunTime(), 0 /* Reset cumulativeExecutionTime because of successful execution */); } // JobCompletedListener implementations. Loading apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +18 −2 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public final class JobServiceContext implements ServiceConnection { private static final long OP_TIMEOUT_MILLIS = 8 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; /** Amount of time the JobScheduler will wait for a job to provide a required notification. */ private static final long NOTIFICATION_TIMEOUT_MILLIS = 10_000L * Build.HW_TIMEOUT_MULTIPLIER; private static final long EXECUTION_DURATION_STAMP_PERIOD_MILLIS = 5 * 60_000L; private static final String[] VERB_STRINGS = { "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED" Loading Loading @@ -191,6 +192,8 @@ public final class JobServiceContext implements ServiceConnection { private long mMaxExecutionTimeMillis; /** Whether this job is required to provide a notification and we're still waiting for it. */ private boolean mAwaitingNotification; /** The last time we updated the job's execution duration, in the elapsed realtime timebase. */ private long mLastExecutionDurationStampTimeElapsed; private long mEstimatedDownloadBytes; private long mEstimatedUploadBytes; Loading Loading @@ -1245,7 +1248,15 @@ public final class JobServiceContext implements ServiceConnection { /* anrMessage */ "required notification not provided", /* triggerAnr */ true); } else { final long timeSinceDurationStampTimeMs = nowElapsed - mLastExecutionDurationStampTimeElapsed; if (timeSinceDurationStampTimeMs < EXECUTION_DURATION_STAMP_PERIOD_MILLIS) { Slog.e(TAG, "Unexpected op timeout while EXECUTING"); } // Update the execution time even if this wasn't the pre-set time. mRunningJob.incrementCumulativeExecutionTime(timeSinceDurationStampTimeMs); mService.mJobs.touchJob(mRunningJob); mLastExecutionDurationStampTimeElapsed = nowElapsed; scheduleOpTimeOutLocked(); } break; Loading Loading @@ -1314,8 +1325,11 @@ public final class JobServiceContext implements ServiceConnection { Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString() + " reschedule=" + reschedule + " reason=" + loggingDebugReason); } final long nowElapsed = sElapsedRealtimeClock.millis(); applyStoppedReasonLocked(loggingDebugReason); completedJob = mRunningJob; completedJob.incrementCumulativeExecutionTime( nowElapsed - mLastExecutionDurationStampTimeElapsed); // Use the JobParameters stop reasons for logging and metric purposes, // but if the job was marked for death, use that reason for rescheduling purposes. // The discrepancy could happen if a job ends up stopping for some reason Loading @@ -1342,7 +1356,7 @@ public final class JobServiceContext implements ServiceConnection { mPreviousJobHadSuccessfulFinish = (loggingInternalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); if (!mPreviousJobHadSuccessfulFinish) { mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis(); mLastUnsuccessfulFinishElapsed = nowElapsed; } mJobPackageTracker.noteInactive(completedJob, loggingInternalStopReason, loggingDebugReason); Loading Loading @@ -1411,6 +1425,7 @@ public final class JobServiceContext implements ServiceConnection { mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED; mDeathMarkInternalStopReason = 0; mDeathMarkDebugReason = null; mLastExecutionDurationStampTimeElapsed = 0; mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; mPendingInternalStopReason = 0; mPendingDebugStopReason = null; Loading Loading @@ -1460,6 +1475,7 @@ public final class JobServiceContext implements ServiceConnection { if (mAwaitingNotification) { minTimeout = Math.min(minTimeout, NOTIFICATION_TIMEOUT_MILLIS); } minTimeout = Math.min(minTimeout, EXECUTION_DURATION_STAMP_PERIOD_MILLIS); timeoutMillis = minTimeout; break; Loading apex/jobscheduler/service/java/com/android/server/job/JobStore.java +11 −3 Original line number Diff line number Diff line Loading @@ -236,7 +236,8 @@ public final class JobStore { convertRtcBoundsToElapsed(utcTimes, elapsedNow); JobStatus newJob = new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second, 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()); 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(), job.getCumulativeExecutionTimeMs()); newJob.prepareLocked(); toAdd.add(newJob); toRemove.add(job); Loading Loading @@ -786,7 +787,7 @@ public final class JobStore { * Write out a tag with data comprising the required fields and bias of this job and * its client. */ private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus) private void addAttributesToJobTag(TypedXmlSerializer out, JobStatus jobStatus) throws IOException { out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId())); out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName()); Loading @@ -813,6 +814,9 @@ public final class JobStore { String.valueOf(jobStatus.getLastSuccessfulRunTime())); out.attribute(null, "lastFailedRunTime", String.valueOf(jobStatus.getLastFailedRunTime())); out.attributeLong(null, "cumulativeExecutionTime", jobStatus.getCumulativeExecutionTimeMs()); } private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) Loading Loading @@ -1190,6 +1194,7 @@ public final class JobStore { int uid, sourceUserId; long lastSuccessfulRunTime; long lastFailedRunTime; long cumulativeExecutionTime; int internalFlags = 0; // Read out job identifier attributes and bias. Loading Loading @@ -1230,6 +1235,9 @@ public final class JobStore { val = parser.getAttributeValue(null, "lastFailedRunTime"); lastFailedRunTime = val == null ? 0 : Long.parseLong(val); cumulativeExecutionTime = parser.getAttributeLong(null, "cumulativeExecutionTime", 0); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); return null; Loading Loading @@ -1402,7 +1410,7 @@ public final class JobStore { builtJob, uid, sourcePackageName, sourceUserId, appBucket, namespace, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTime, (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0); if (jobWorkItems != null) { for (int i = 0; i < jobWorkItems.size(); ++i) { Loading apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +39 −5 Original line number Diff line number Diff line Loading @@ -375,6 +375,11 @@ public final class JobStatus { * and is thus considered demoted from whatever privileged state it had in the past. */ public static final int INTERNAL_FLAG_DEMOTED_BY_USER = 1 << 1; /** * Flag for {@link #mInternalFlags}: this job is demoted by the system * from running as a user-initiated job. */ public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2; /** Minimum difference between start and end time to have flexible constraint */ @VisibleForTesting Loading @@ -385,6 +390,12 @@ public final class JobStatus { */ private int mInternalFlags; /** * The cumulative amount of time this job has run for, including previous executions. * This is reset for periodic jobs upon a successful job execution. */ private long mCumulativeExecutionTimeMs; // These are filled in by controllers when preparing for execution. public ArraySet<Uri> changedUris; public ArraySet<String> changedAuthorities; Loading Loading @@ -550,7 +561,8 @@ public final class JobStatus { int sourceUserId, int standbyBucket, @Nullable String namespace, String tag, int numFailures, int numSystemStops, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags, long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, int internalFlags, int dynamicConstraints) { this.job = job; this.callingUid = callingUid; Loading Loading @@ -650,6 +662,8 @@ public final class JobStatus { mReadyDynamicSatisfied = false; } mCumulativeExecutionTimeMs = cumulativeExecutionTimeMs; mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; Loading Loading @@ -684,6 +698,7 @@ public final class JobStatus { jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), jobStatus.getCumulativeExecutionTimeMs(), jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints); mPersistedUtcTimes = jobStatus.mPersistedUtcTimes; if (jobStatus.mPersistedUtcTimes != null) { Loading Loading @@ -711,13 +726,15 @@ public final class JobStatus { int standbyBucket, @Nullable String namespace, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, Pair<Long, Long> persistedExecutionTimesUTC, int innerFlags, int dynamicConstraints) { this(job, callingUid, sourcePkgName, sourceUserId, standbyBucket, namespace, sourceTag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints); lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs, innerFlags, dynamicConstraints); // Only during initial inflation do we record the UTC-timebase execution bounds // read from the persistent store. If we ever have to recreate the JobStatus on Loading @@ -735,14 +752,16 @@ public final class JobStatus { public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops, long lastSuccessfulRunTime, long lastFailedRunTime) { long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getStandbyBucket(), rescheduling.getNamespace(), rescheduling.getSourceTag(), numFailures, numSystemStops, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(), lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs, rescheduling.getInternalFlags(), rescheduling.mDynamicConstraints); } Loading Loading @@ -780,6 +799,7 @@ public final class JobStatus { standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, /* cumulativeExecutionTime */ 0, /*innerFlags=*/ 0, /* dynamicConstraints */ 0); } Loading Loading @@ -1312,6 +1332,14 @@ public final class JobStatus { return job.isPersisted(); } public long getCumulativeExecutionTimeMs() { return mCumulativeExecutionTimeMs; } public void incrementCumulativeExecutionTime(long incrementMs) { mCumulativeExecutionTimeMs += incrementMs; } public long getEarliestRunTime() { return earliestRunTimeElapsedMillis; } Loading Loading @@ -1405,7 +1433,8 @@ public final class JobStatus { */ public boolean shouldTreatAsUserInitiatedJob() { return getJob().isUserInitiated() && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0; && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0 && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) == 0; } /** Loading Loading @@ -2655,6 +2684,11 @@ public final class JobStatus { pw.print(", original latest="); formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, nowElapsed); pw.println(); if (mCumulativeExecutionTimeMs != 0) { pw.print("Cumulative execution time="); TimeUtils.formatDuration(mCumulativeExecutionTimeMs, pw); pw.println(); } if (numFailures != 0) { pw.print("Num failures: "); pw.println(numFailures); } Loading services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -337,6 +337,50 @@ public class JobSchedulerServiceTest { mService.getMaxJobExecutionTimeMs(jobUIDT)); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job that is no longer allowed to run as a user-initiated job after it hits * the cumulative execution limit. */ @Test public void testGetRescheduleJobForFailure_cumulativeExecution() { JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo() .setUserInitiated(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); assertTrue(originalJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 0 JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 50% of limit rescheduledJob.incrementCumulativeExecutionTime( mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 99.999999% of limit rescheduledJob.incrementCumulativeExecutionTime( mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2 - 1); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 100+% of limit rescheduledJob.incrementCumulativeExecutionTime(2); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob()); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +27 −3 Original line number Diff line number Diff line Loading @@ -482,6 +482,7 @@ public class JobSchedulerService extends com.android.server.SystemService case Constants.KEY_RUNTIME_UI_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS: case Constants.KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS: case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS: if (!runtimeUpdated) { mConstants.updateRuntimeConstantsLocked(); Loading Loading @@ -582,6 +583,8 @@ public class JobSchedulerService extends com.android.server.SystemService "runtime_min_ui_data_transfer_guarantee_buffer_factor"; private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = "runtime_min_ui_data_transfer_guarantee_ms"; private static final String KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS = "runtime_cumulative_ui_limit_ms"; private static final String KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = "runtime_use_data_estimates_for_limits"; Loading Loading @@ -622,6 +625,7 @@ public class JobSchedulerService extends com.android.server.SystemService 1.35f; public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS); public static final long DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS = 24 * HOUR_IN_MILLIS; public static final boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false; static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true; static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000; Loading Loading @@ -759,6 +763,12 @@ public class JobSchedulerService extends com.android.server.SystemService public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS; /** * The maximum amount of cumulative time we will let a user-initiated job run for * before downgrading it. */ public long RUNTIME_CUMULATIVE_UI_LIMIT_MS = DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS; /** * Whether to use data estimates to determine execution limits for execution limits. */ Loading Loading @@ -881,6 +891,7 @@ public class JobSchedulerService extends com.android.server.SystemService KEY_RUNTIME_MIN_UI_GUARANTEE_MS, KEY_RUNTIME_UI_LIMIT_MS, KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS); // Make sure min runtime for regular jobs is at least 10 minutes. Loading Loading @@ -915,6 +926,11 @@ public class JobSchedulerService extends com.android.server.SystemService properties.getLong( KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS)); // The cumulative runtime limit should be at least the max execution limit. RUNTIME_CUMULATIVE_UI_LIMIT_MS = Math.max(RUNTIME_UI_LIMIT_MS, properties.getLong( KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS)); RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean( KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, Loading Loading @@ -972,6 +988,7 @@ public class JobSchedulerService extends com.android.server.SystemService RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println(); pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println(); pw.print(KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, RUNTIME_CUMULATIVE_UI_LIMIT_MS).println(); pw.print(KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).println(); Loading Loading @@ -2452,11 +2469,16 @@ public class JobSchedulerService extends com.android.server.SystemService JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(), failureToReschedule.getCumulativeExecutionTimeMs()); if (stopReason == JobParameters.STOP_REASON_USER) { // Demote all jobs to regular for user stops so they don't keep privileges. newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); } if (newJob.getCumulativeExecutionTimeMs() >= mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS && newJob.shouldTreatAsUserInitiatedJob()) { newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ); } if (job.isPeriodic()) { newJob.setOriginalLatestRunTimeElapsed( failureToReschedule.getOriginalLatestRunTimeElapsed()); Loading Loading @@ -2548,7 +2570,8 @@ public class JobSchedulerService extends com.android.server.SystemService elapsedNow + period - flex, elapsedNow + period, 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); periodicToReschedule.getLastFailedRunTime(), 0 /* Reset cumulativeExecutionTime because of successful execution */); } final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed Loading @@ -2563,7 +2586,8 @@ public class JobSchedulerService extends com.android.server.SystemService newEarliestRunTimeElapsed, newLatestRuntimeElapsed, 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); periodicToReschedule.getLastFailedRunTime(), 0 /* Reset cumulativeExecutionTime because of successful execution */); } // JobCompletedListener implementations. Loading
apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +18 −2 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public final class JobServiceContext implements ServiceConnection { private static final long OP_TIMEOUT_MILLIS = 8 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; /** Amount of time the JobScheduler will wait for a job to provide a required notification. */ private static final long NOTIFICATION_TIMEOUT_MILLIS = 10_000L * Build.HW_TIMEOUT_MULTIPLIER; private static final long EXECUTION_DURATION_STAMP_PERIOD_MILLIS = 5 * 60_000L; private static final String[] VERB_STRINGS = { "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED" Loading Loading @@ -191,6 +192,8 @@ public final class JobServiceContext implements ServiceConnection { private long mMaxExecutionTimeMillis; /** Whether this job is required to provide a notification and we're still waiting for it. */ private boolean mAwaitingNotification; /** The last time we updated the job's execution duration, in the elapsed realtime timebase. */ private long mLastExecutionDurationStampTimeElapsed; private long mEstimatedDownloadBytes; private long mEstimatedUploadBytes; Loading Loading @@ -1245,7 +1248,15 @@ public final class JobServiceContext implements ServiceConnection { /* anrMessage */ "required notification not provided", /* triggerAnr */ true); } else { final long timeSinceDurationStampTimeMs = nowElapsed - mLastExecutionDurationStampTimeElapsed; if (timeSinceDurationStampTimeMs < EXECUTION_DURATION_STAMP_PERIOD_MILLIS) { Slog.e(TAG, "Unexpected op timeout while EXECUTING"); } // Update the execution time even if this wasn't the pre-set time. mRunningJob.incrementCumulativeExecutionTime(timeSinceDurationStampTimeMs); mService.mJobs.touchJob(mRunningJob); mLastExecutionDurationStampTimeElapsed = nowElapsed; scheduleOpTimeOutLocked(); } break; Loading Loading @@ -1314,8 +1325,11 @@ public final class JobServiceContext implements ServiceConnection { Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString() + " reschedule=" + reschedule + " reason=" + loggingDebugReason); } final long nowElapsed = sElapsedRealtimeClock.millis(); applyStoppedReasonLocked(loggingDebugReason); completedJob = mRunningJob; completedJob.incrementCumulativeExecutionTime( nowElapsed - mLastExecutionDurationStampTimeElapsed); // Use the JobParameters stop reasons for logging and metric purposes, // but if the job was marked for death, use that reason for rescheduling purposes. // The discrepancy could happen if a job ends up stopping for some reason Loading @@ -1342,7 +1356,7 @@ public final class JobServiceContext implements ServiceConnection { mPreviousJobHadSuccessfulFinish = (loggingInternalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); if (!mPreviousJobHadSuccessfulFinish) { mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis(); mLastUnsuccessfulFinishElapsed = nowElapsed; } mJobPackageTracker.noteInactive(completedJob, loggingInternalStopReason, loggingDebugReason); Loading Loading @@ -1411,6 +1425,7 @@ public final class JobServiceContext implements ServiceConnection { mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED; mDeathMarkInternalStopReason = 0; mDeathMarkDebugReason = null; mLastExecutionDurationStampTimeElapsed = 0; mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; mPendingInternalStopReason = 0; mPendingDebugStopReason = null; Loading Loading @@ -1460,6 +1475,7 @@ public final class JobServiceContext implements ServiceConnection { if (mAwaitingNotification) { minTimeout = Math.min(minTimeout, NOTIFICATION_TIMEOUT_MILLIS); } minTimeout = Math.min(minTimeout, EXECUTION_DURATION_STAMP_PERIOD_MILLIS); timeoutMillis = minTimeout; break; Loading
apex/jobscheduler/service/java/com/android/server/job/JobStore.java +11 −3 Original line number Diff line number Diff line Loading @@ -236,7 +236,8 @@ public final class JobStore { convertRtcBoundsToElapsed(utcTimes, elapsedNow); JobStatus newJob = new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second, 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()); 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(), job.getCumulativeExecutionTimeMs()); newJob.prepareLocked(); toAdd.add(newJob); toRemove.add(job); Loading Loading @@ -786,7 +787,7 @@ public final class JobStore { * Write out a tag with data comprising the required fields and bias of this job and * its client. */ private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus) private void addAttributesToJobTag(TypedXmlSerializer out, JobStatus jobStatus) throws IOException { out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId())); out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName()); Loading @@ -813,6 +814,9 @@ public final class JobStore { String.valueOf(jobStatus.getLastSuccessfulRunTime())); out.attribute(null, "lastFailedRunTime", String.valueOf(jobStatus.getLastFailedRunTime())); out.attributeLong(null, "cumulativeExecutionTime", jobStatus.getCumulativeExecutionTimeMs()); } private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) Loading Loading @@ -1190,6 +1194,7 @@ public final class JobStore { int uid, sourceUserId; long lastSuccessfulRunTime; long lastFailedRunTime; long cumulativeExecutionTime; int internalFlags = 0; // Read out job identifier attributes and bias. Loading Loading @@ -1230,6 +1235,9 @@ public final class JobStore { val = parser.getAttributeValue(null, "lastFailedRunTime"); lastFailedRunTime = val == null ? 0 : Long.parseLong(val); cumulativeExecutionTime = parser.getAttributeLong(null, "cumulativeExecutionTime", 0); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); return null; Loading Loading @@ -1402,7 +1410,7 @@ public final class JobStore { builtJob, uid, sourcePackageName, sourceUserId, appBucket, namespace, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime, lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTime, (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0); if (jobWorkItems != null) { for (int i = 0; i < jobWorkItems.size(); ++i) { Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +39 −5 Original line number Diff line number Diff line Loading @@ -375,6 +375,11 @@ public final class JobStatus { * and is thus considered demoted from whatever privileged state it had in the past. */ public static final int INTERNAL_FLAG_DEMOTED_BY_USER = 1 << 1; /** * Flag for {@link #mInternalFlags}: this job is demoted by the system * from running as a user-initiated job. */ public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2; /** Minimum difference between start and end time to have flexible constraint */ @VisibleForTesting Loading @@ -385,6 +390,12 @@ public final class JobStatus { */ private int mInternalFlags; /** * The cumulative amount of time this job has run for, including previous executions. * This is reset for periodic jobs upon a successful job execution. */ private long mCumulativeExecutionTimeMs; // These are filled in by controllers when preparing for execution. public ArraySet<Uri> changedUris; public ArraySet<String> changedAuthorities; Loading Loading @@ -550,7 +561,8 @@ public final class JobStatus { int sourceUserId, int standbyBucket, @Nullable String namespace, String tag, int numFailures, int numSystemStops, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags, long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, int internalFlags, int dynamicConstraints) { this.job = job; this.callingUid = callingUid; Loading Loading @@ -650,6 +662,8 @@ public final class JobStatus { mReadyDynamicSatisfied = false; } mCumulativeExecutionTimeMs = cumulativeExecutionTimeMs; mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; Loading Loading @@ -684,6 +698,7 @@ public final class JobStatus { jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), jobStatus.getCumulativeExecutionTimeMs(), jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints); mPersistedUtcTimes = jobStatus.mPersistedUtcTimes; if (jobStatus.mPersistedUtcTimes != null) { Loading Loading @@ -711,13 +726,15 @@ public final class JobStatus { int standbyBucket, @Nullable String namespace, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, Pair<Long, Long> persistedExecutionTimesUTC, int innerFlags, int dynamicConstraints) { this(job, callingUid, sourcePkgName, sourceUserId, standbyBucket, namespace, sourceTag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints); lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs, innerFlags, dynamicConstraints); // Only during initial inflation do we record the UTC-timebase execution bounds // read from the persistent store. If we ever have to recreate the JobStatus on Loading @@ -735,14 +752,16 @@ public final class JobStatus { public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops, long lastSuccessfulRunTime, long lastFailedRunTime) { long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getStandbyBucket(), rescheduling.getNamespace(), rescheduling.getSourceTag(), numFailures, numSystemStops, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(), lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs, rescheduling.getInternalFlags(), rescheduling.mDynamicConstraints); } Loading Loading @@ -780,6 +799,7 @@ public final class JobStatus { standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, /* cumulativeExecutionTime */ 0, /*innerFlags=*/ 0, /* dynamicConstraints */ 0); } Loading Loading @@ -1312,6 +1332,14 @@ public final class JobStatus { return job.isPersisted(); } public long getCumulativeExecutionTimeMs() { return mCumulativeExecutionTimeMs; } public void incrementCumulativeExecutionTime(long incrementMs) { mCumulativeExecutionTimeMs += incrementMs; } public long getEarliestRunTime() { return earliestRunTimeElapsedMillis; } Loading Loading @@ -1405,7 +1433,8 @@ public final class JobStatus { */ public boolean shouldTreatAsUserInitiatedJob() { return getJob().isUserInitiated() && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0; && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0 && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) == 0; } /** Loading Loading @@ -2655,6 +2684,11 @@ public final class JobStatus { pw.print(", original latest="); formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, nowElapsed); pw.println(); if (mCumulativeExecutionTimeMs != 0) { pw.print("Cumulative execution time="); TimeUtils.formatDuration(mCumulativeExecutionTimeMs, pw); pw.println(); } if (numFailures != 0) { pw.print("Num failures: "); pw.println(numFailures); } Loading
services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -337,6 +337,50 @@ public class JobSchedulerServiceTest { mService.getMaxJobExecutionTimeMs(jobUIDT)); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job that is no longer allowed to run as a user-initiated job after it hits * the cumulative execution limit. */ @Test public void testGetRescheduleJobForFailure_cumulativeExecution() { JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo() .setUserInitiated(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); assertTrue(originalJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 0 JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 50% of limit rescheduledJob.incrementCumulativeExecutionTime( mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 99.999999% of limit rescheduledJob.incrementCumulativeExecutionTime( mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2 - 1); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob()); // Cumulative time = 100+% of limit rescheduledJob.incrementCumulativeExecutionTime(2); rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN); assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob()); } /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} Loading