Merge "Scheduling start alarm when job starts off out of quota." into qt-dev
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index dc2e6d5..0df2c83 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -459,6 +459,7 @@
optional bool is_charging = 1;
optional bool is_in_parole = 2;
+ optional int64 elapsed_realtime = 6;
// List of UIDs currently in the foreground.
repeated int32 foreground_uids = 3;
@@ -478,6 +479,16 @@
}
repeated TrackedJob tracked_jobs = 4;
+ message AlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the listener is waiting for an alarm or not.
+ optional bool is_waiting = 1;
+ // The time at which the alarm should go off, in the elapsed realtime timebase. Only
+ // valid if is_waiting is true.
+ optional int64 trigger_time_elapsed = 2;
+ }
+
message ExecutionStats {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -567,6 +578,8 @@
repeated TimingSession saved_sessions = 3;
repeated ExecutionStats execution_stats = 4;
+
+ optional AlarmListener in_quota_alarm_listener = 5;
}
repeated PackageStats package_stats = 5;
}
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index ccd1db4..11f0939 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -511,17 +511,28 @@
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
// Still need to track jobs even if mShouldThrottle is false in case it's set to true at
// some point.
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs == null) {
jobs = new ArraySet<>();
- mTrackedJobs.add(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), jobs);
+ mTrackedJobs.add(userId, pkgName, jobs);
}
jobs.add(jobStatus);
jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
- jobStatus.setQuotaConstraintSatisfied(!mShouldThrottle || isWithinQuotaLocked(jobStatus));
+ if (mShouldThrottle) {
+ final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
+ jobStatus.setQuotaConstraintSatisfied(isWithinQuota);
+ if (!isWithinQuota) {
+ maybeScheduleStartAlarmLocked(userId, pkgName,
+ getEffectiveStandbyBucket(jobStatus));
+ }
+ } else {
+ // QuotaController isn't throttling, so always set to true.
+ jobStatus.setQuotaConstraintSatisfied(true);
+ }
}
@Override
@@ -1628,6 +1639,9 @@
if (isActive()) {
pw.print("started at ");
pw.print(mStartTimeElapsed);
+ pw.print(" (");
+ pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed);
+ pw.print("ms ago)");
} else {
pw.print("NOT active");
}
@@ -1937,6 +1951,7 @@
pw.println("Is throttling: " + mShouldThrottle);
pw.println("Is charging: " + mChargeTracker.isCharging());
pw.println("In parole: " + mInParole);
+ pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
pw.println();
pw.print("Foreground UIDs: ");
@@ -2030,6 +2045,26 @@
}
}
pw.decreaseIndent();
+
+ pw.println();
+ pw.println("In quota alarms:");
+ pw.increaseIndent();
+ for (int u = 0; u < mInQuotaAlarmListeners.numUsers(); ++u) {
+ final int userId = mInQuotaAlarmListeners.keyAt(u);
+ for (int p = 0; p < mInQuotaAlarmListeners.numPackagesForUser(userId); ++p) {
+ final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
+ QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
+
+ pw.print(string(userId, pkgName));
+ pw.print(": ");
+ if (alarmListener.isWaiting()) {
+ pw.println(alarmListener.getTriggerTimeElapsed());
+ } else {
+ pw.println("NOT WAITING");
+ }
+ }
+ }
+ pw.decreaseIndent();
}
@Override
@@ -2040,6 +2075,8 @@
proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
+ proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
+ sElapsedRealtimeClock.millis());
for (int i = 0; i < mForegroundUids.size(); ++i) {
proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
@@ -2132,6 +2169,18 @@
}
}
+ QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
+ if (alarmListener != null) {
+ final long alToken = proto.start(
+ StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
+ proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
+ alarmListener.isWaiting());
+ proto.write(
+ StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
+ alarmListener.getTriggerTimeElapsed());
+ proto.end(alToken);
+ }
+
proto.end(psToken);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index f492d13..7c30f25 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -2197,4 +2197,51 @@
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
verify(handler, never()).sendMessageDelayed(any(), anyInt());
}
+
+ /**
+ * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
+ * count quota.
+ */
+ @Test
+ public void testStartAlarmScheduled_JobCount_AllowedTime() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ final long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final int standbyBucket = WORKING_INDEX;
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // No sessions saved yet.
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Ran jobs up to the job limit. All of them should be allowed to run.
+ for (int i = 0; i < mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; ++i) {
+ JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ mQuotaController.prepareForExecutionLocked(job);
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ }
+ // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // The app is now out of job count quota
+ JobStatus throttledJob = createJobStatus(
+ "testStartAlarmScheduled_JobCount_AllowedTime", 42);
+ mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
+ assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+ SOURCE_PACKAGE, standbyBucket);
+ final long expectedWorkingAlarmTime =
+ stats.jobCountExpirationTimeElapsed + mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS;
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
}