Loading core/proto/android/server/jobscheduler.proto +13 −0 Original line number Diff line number Diff line Loading @@ -459,6 +459,7 @@ message StateControllerProto { 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; Loading @@ -478,6 +479,16 @@ message StateControllerProto { } 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; Loading Loading @@ -567,6 +578,8 @@ message StateControllerProto { repeated TimingSession saved_sessions = 3; repeated ExecutionStats execution_stats = 4; optional AlarmListener in_quota_alarm_listener = 5; } repeated PackageStats package_stats = 5; } Loading services/core/java/com/android/server/job/controllers/QuotaController.java +53 −4 Original line number Diff line number Diff line Loading @@ -511,17 +511,28 @@ public final class QuotaController extends StateController { @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 Loading Loading @@ -1628,6 +1639,9 @@ public final class QuotaController extends StateController { 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"); } Loading Loading @@ -1937,6 +1951,7 @@ public final class QuotaController extends StateController { 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: "); Loading Loading @@ -2030,6 +2045,26 @@ public final class QuotaController extends StateController { } } 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 Loading @@ -2040,6 +2075,8 @@ public final class QuotaController extends StateController { 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, Loading Loading @@ -2132,6 +2169,18 @@ public final class QuotaController extends StateController { } } 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); } } Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +47 −0 Original line number Diff line number Diff line Loading @@ -2197,4 +2197,51 @@ public class QuotaControllerTest { 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()); } } Loading
core/proto/android/server/jobscheduler.proto +13 −0 Original line number Diff line number Diff line Loading @@ -459,6 +459,7 @@ message StateControllerProto { 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; Loading @@ -478,6 +479,16 @@ message StateControllerProto { } 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; Loading Loading @@ -567,6 +578,8 @@ message StateControllerProto { repeated TimingSession saved_sessions = 3; repeated ExecutionStats execution_stats = 4; optional AlarmListener in_quota_alarm_listener = 5; } repeated PackageStats package_stats = 5; } Loading
services/core/java/com/android/server/job/controllers/QuotaController.java +53 −4 Original line number Diff line number Diff line Loading @@ -511,17 +511,28 @@ public final class QuotaController extends StateController { @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 Loading Loading @@ -1628,6 +1639,9 @@ public final class QuotaController extends StateController { 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"); } Loading Loading @@ -1937,6 +1951,7 @@ public final class QuotaController extends StateController { 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: "); Loading Loading @@ -2030,6 +2045,26 @@ public final class QuotaController extends StateController { } } 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 Loading @@ -2040,6 +2075,8 @@ public final class QuotaController extends StateController { 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, Loading Loading @@ -2132,6 +2169,18 @@ public final class QuotaController extends StateController { } } 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); } } Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +47 −0 Original line number Diff line number Diff line Loading @@ -2197,4 +2197,51 @@ public class QuotaControllerTest { 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()); } }