Loading core/proto/android/server/jobscheduler.proto +5 −3 Original line number Diff line number Diff line Loading @@ -417,7 +417,8 @@ message StateControllerProto { optional int64 start_time_elapsed = 1; optional int64 end_time_elapsed = 2; optional int32 job_count = 3; // The number of background jobs that ran during this session. optional int32 bg_job_count = 3; } message Timer { Loading @@ -428,8 +429,9 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many are currently running. Valid only if the device is_active is true. optional int32 job_count = 4; // How many background jobs are currently running. Valid only if the device is_active // is true. optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; } Loading services/core/java/com/android/server/job/controllers/QuotaController.java +49 −36 Original line number Diff line number Diff line Loading @@ -151,8 +151,7 @@ public final class QuotaController extends StateController { return "<" + userId + ">" + packageName; } @VisibleForTesting static final class Package { private static final class Package { public final String packageName; public final int userId; Loading Loading @@ -387,8 +386,9 @@ public final class QuotaController extends StateController { private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { final int standbyBucket = getEffectiveStandbyBucket(jobStatus); return isWithinQuotaLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); // Jobs for the active app should always be able to run. return jobStatus.uidActive || isWithinQuotaLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, Loading Loading @@ -579,7 +579,10 @@ public final class QuotaController extends StateController { boolean changed = false; for (int i = jobs.size() - 1; i >= 0; --i) { final JobStatus js = jobs.valueAt(i); if (realStandbyBucket == getEffectiveStandbyBucket(js)) { if (js.uidActive) { // Jobs for the active app should always be able to run. changed |= js.setQuotaConstraintSatisfied(true); } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) { changed |= js.setQuotaConstraintSatisfied(realInQuota); } else { // This job is somehow exempted. Need to determine its own quota status. Loading Loading @@ -765,18 +768,18 @@ public final class QuotaController extends StateController { public final long startTimeElapsed; // End timestamp in elapsed realtime timebase. public final long endTimeElapsed; // How many jobs ran during this session. public final int jobCount; // How many background jobs ran during this session. public final int bgJobCount; TimingSession(long startElapsed, long endElapsed, int jobCount) { this.startTimeElapsed = startElapsed; this.endTimeElapsed = endElapsed; this.jobCount = jobCount; this.bgJobCount = jobCount; } @Override public String toString() { return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount + "}"; } Loading @@ -786,7 +789,7 @@ public final class QuotaController extends StateController { TimingSession other = (TimingSession) obj; return startTimeElapsed == other.startTimeElapsed && endTimeElapsed == other.endTimeElapsed && jobCount == other.jobCount; && bgJobCount == other.bgJobCount; } else { return false; } Loading @@ -794,7 +797,7 @@ public final class QuotaController extends StateController { @Override public int hashCode() { return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount}); return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount}); } public void dump(IndentingPrintWriter pw) { Loading @@ -804,8 +807,8 @@ public final class QuotaController extends StateController { pw.print(" ("); pw.print(endTimeElapsed - startTimeElapsed); pw.print("), "); pw.print(jobCount); pw.print(" jobs."); pw.print(bgJobCount); pw.print(" bg jobs."); pw.println(); } Loading @@ -816,7 +819,8 @@ public final class QuotaController extends StateController { startTimeElapsed); proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, endTimeElapsed); proto.write(StateControllerProto.QuotaController.TimingSession.JOB_COUNT, jobCount); proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, bgJobCount); proto.end(token); } Loading @@ -825,23 +829,32 @@ public final class QuotaController extends StateController { private final class Timer { private final Package mPkg; // List of jobs currently running for this package. private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); // List of jobs currently running for this app that started when the app wasn't in the // foreground. private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); private long mStartTimeElapsed; private int mJobCount; private int mBgJobCount; Timer(int userId, String packageName) { mPkg = new Package(userId, packageName); } void startTrackingJob(@NonNull JobStatus jobStatus) { if (jobStatus.uidActive) { // We intentionally don't pay attention to fg state changes after a job has started. if (DEBUG) { Slog.v(TAG, "Timer ignoring " + jobStatus.toShortString() + " because uidActive"); } return; } if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); synchronized (mLock) { // Always track jobs, even when charging. mRunningJobs.add(jobStatus); mRunningBgJobs.add(jobStatus); if (!mChargeTracker.isCharging()) { mJobCount++; if (mRunningJobs.size() == 1) { mBgJobCount++; if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); scheduleCutoff(); Loading @@ -853,7 +866,7 @@ public final class QuotaController extends StateController { void stopTrackingJob(@NonNull JobStatus jobStatus) { if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); synchronized (mLock) { if (mRunningJobs.size() == 0) { if (mRunningBgJobs.size() == 0) { // maybeStopTrackingJobLocked can be called when an app cancels a job, so a // timer may not be running when it's asked to stop tracking a job. if (DEBUG) { Loading @@ -861,8 +874,8 @@ public final class QuotaController extends StateController { } return; } mRunningJobs.remove(jobStatus); if (!mChargeTracker.isCharging() && mRunningJobs.size() == 0) { if (mRunningBgJobs.remove(jobStatus) && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) { emitSessionLocked(sElapsedRealtimeClock.millis()); cancelCutoff(); } Loading @@ -870,13 +883,13 @@ public final class QuotaController extends StateController { } private void emitSessionLocked(long nowElapsed) { if (mJobCount <= 0) { if (mBgJobCount <= 0) { // Nothing to emit. return; } TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mJobCount); TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); saveTimingSession(mPkg.userId, mPkg.packageName, ts); mJobCount = 0; mBgJobCount = 0; // Don't reset the tracked jobs list as we need to keep tracking the current number // of jobs. // However, cancel the currently scheduled cutoff since it's not currently useful. Loading @@ -889,7 +902,7 @@ public final class QuotaController extends StateController { */ public boolean isActive() { synchronized (mLock) { return mJobCount > 0; return mBgJobCount > 0; } } Loading @@ -905,12 +918,12 @@ public final class QuotaController extends StateController { emitSessionLocked(nowElapsed); } else { // Start timing from unplug. if (mRunningJobs.size() > 0) { if (mRunningBgJobs.size() > 0) { mStartTimeElapsed = nowElapsed; // NOTE: this does have the unfortunate consequence that if the device is // repeatedly plugged in and unplugged, the job count for a package may be // artificially high. mJobCount = mRunningJobs.size(); mBgJobCount = mRunningBgJobs.size(); // Schedule cutoff since we're now actively tracking for quotas again. scheduleCutoff(); } Loading Loading @@ -958,12 +971,12 @@ public final class QuotaController extends StateController { pw.print("NOT active"); } pw.print(", "); pw.print(mJobCount); pw.print(" running jobs"); pw.print(mBgJobCount); pw.print(" running bg jobs"); pw.println(); pw.increaseIndent(); for (int i = 0; i < mRunningJobs.size(); i++) { JobStatus js = mRunningJobs.valueAt(i); for (int i = 0; i < mRunningBgJobs.size(); i++) { JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { pw.println(js.toShortString()); } Loading @@ -979,9 +992,9 @@ public final class QuotaController extends StateController { proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, mStartTimeElapsed); proto.write(StateControllerProto.QuotaController.Timer.JOB_COUNT, mJobCount); for (int i = 0; i < mRunningJobs.size(); i++) { JobStatus js = mRunningJobs.valueAt(i); proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); for (int i = 0; i < mRunningBgJobs.size(); i++) { JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { js.writeToShortProto(proto, StateControllerProto.QuotaController.Timer.RUNNING_JOBS); Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +133 −0 Original line number Diff line number Diff line Loading @@ -780,6 +780,139 @@ public class QuotaControllerTest { assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ @Test public void testTimerTracking_AllBackground() { setDischarging(); JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); List<TimingSession> expected = new ArrayList<>(); // Test single job. long start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(5 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); // Test overlapping jobs. JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2); mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3); mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); advanceElapsedClock(SECOND_IN_MILLIS); start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobStatus2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobStatus3); advanceElapsedClock(20 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** Tests that Timers don't count foreground jobs. */ @Test public void testTimerTracking_AllForeground() { setDischarging(); JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1); jobStatus.uidActive = true; mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(5 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Tests that Timers properly track overlapping foreground and background jobs. */ @Test public void testTimerTracking_ForegroundAndBackground() { setDischarging(); JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1); JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2); JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3); jobFg3.uidActive = true; mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); List<TimingSession> expected = new ArrayList<>(); // UID starts out inactive. long start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobBg1); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); advanceElapsedClock(SECOND_IN_MILLIS); // Bg job starts while inactive, spans an entire active session, and ends after the // active session. // Fg job starts after the bg job and ends before the bg job. // Entire bg job duration should be counted since it started before active session. However, // count should only be 1 since Timer shouldn't count fg jobs. start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.prepareForExecutionLocked(jobBg2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobFg3); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); advanceElapsedClock(SECOND_IN_MILLIS); // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes // "inactive" and then bg job 2 starts. Then fg job ends. // This should result in two TimingSessions with a count of one each. start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); mQuotaController.prepareForExecutionLocked(jobBg1); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobFg3); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobBg2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches * its quota. Loading Loading
core/proto/android/server/jobscheduler.proto +5 −3 Original line number Diff line number Diff line Loading @@ -417,7 +417,8 @@ message StateControllerProto { optional int64 start_time_elapsed = 1; optional int64 end_time_elapsed = 2; optional int32 job_count = 3; // The number of background jobs that ran during this session. optional int32 bg_job_count = 3; } message Timer { Loading @@ -428,8 +429,9 @@ message StateControllerProto { optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many are currently running. Valid only if the device is_active is true. optional int32 job_count = 4; // How many background jobs are currently running. Valid only if the device is_active // is true. optional int32 bg_job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; } Loading
services/core/java/com/android/server/job/controllers/QuotaController.java +49 −36 Original line number Diff line number Diff line Loading @@ -151,8 +151,7 @@ public final class QuotaController extends StateController { return "<" + userId + ">" + packageName; } @VisibleForTesting static final class Package { private static final class Package { public final String packageName; public final int userId; Loading Loading @@ -387,8 +386,9 @@ public final class QuotaController extends StateController { private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { final int standbyBucket = getEffectiveStandbyBucket(jobStatus); return isWithinQuotaLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); // Jobs for the active app should always be able to run. return jobStatus.uidActive || isWithinQuotaLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, Loading Loading @@ -579,7 +579,10 @@ public final class QuotaController extends StateController { boolean changed = false; for (int i = jobs.size() - 1; i >= 0; --i) { final JobStatus js = jobs.valueAt(i); if (realStandbyBucket == getEffectiveStandbyBucket(js)) { if (js.uidActive) { // Jobs for the active app should always be able to run. changed |= js.setQuotaConstraintSatisfied(true); } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) { changed |= js.setQuotaConstraintSatisfied(realInQuota); } else { // This job is somehow exempted. Need to determine its own quota status. Loading Loading @@ -765,18 +768,18 @@ public final class QuotaController extends StateController { public final long startTimeElapsed; // End timestamp in elapsed realtime timebase. public final long endTimeElapsed; // How many jobs ran during this session. public final int jobCount; // How many background jobs ran during this session. public final int bgJobCount; TimingSession(long startElapsed, long endElapsed, int jobCount) { this.startTimeElapsed = startElapsed; this.endTimeElapsed = endElapsed; this.jobCount = jobCount; this.bgJobCount = jobCount; } @Override public String toString() { return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount + "}"; } Loading @@ -786,7 +789,7 @@ public final class QuotaController extends StateController { TimingSession other = (TimingSession) obj; return startTimeElapsed == other.startTimeElapsed && endTimeElapsed == other.endTimeElapsed && jobCount == other.jobCount; && bgJobCount == other.bgJobCount; } else { return false; } Loading @@ -794,7 +797,7 @@ public final class QuotaController extends StateController { @Override public int hashCode() { return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount}); return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount}); } public void dump(IndentingPrintWriter pw) { Loading @@ -804,8 +807,8 @@ public final class QuotaController extends StateController { pw.print(" ("); pw.print(endTimeElapsed - startTimeElapsed); pw.print("), "); pw.print(jobCount); pw.print(" jobs."); pw.print(bgJobCount); pw.print(" bg jobs."); pw.println(); } Loading @@ -816,7 +819,8 @@ public final class QuotaController extends StateController { startTimeElapsed); proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, endTimeElapsed); proto.write(StateControllerProto.QuotaController.TimingSession.JOB_COUNT, jobCount); proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, bgJobCount); proto.end(token); } Loading @@ -825,23 +829,32 @@ public final class QuotaController extends StateController { private final class Timer { private final Package mPkg; // List of jobs currently running for this package. private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); // List of jobs currently running for this app that started when the app wasn't in the // foreground. private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); private long mStartTimeElapsed; private int mJobCount; private int mBgJobCount; Timer(int userId, String packageName) { mPkg = new Package(userId, packageName); } void startTrackingJob(@NonNull JobStatus jobStatus) { if (jobStatus.uidActive) { // We intentionally don't pay attention to fg state changes after a job has started. if (DEBUG) { Slog.v(TAG, "Timer ignoring " + jobStatus.toShortString() + " because uidActive"); } return; } if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); synchronized (mLock) { // Always track jobs, even when charging. mRunningJobs.add(jobStatus); mRunningBgJobs.add(jobStatus); if (!mChargeTracker.isCharging()) { mJobCount++; if (mRunningJobs.size() == 1) { mBgJobCount++; if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); scheduleCutoff(); Loading @@ -853,7 +866,7 @@ public final class QuotaController extends StateController { void stopTrackingJob(@NonNull JobStatus jobStatus) { if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); synchronized (mLock) { if (mRunningJobs.size() == 0) { if (mRunningBgJobs.size() == 0) { // maybeStopTrackingJobLocked can be called when an app cancels a job, so a // timer may not be running when it's asked to stop tracking a job. if (DEBUG) { Loading @@ -861,8 +874,8 @@ public final class QuotaController extends StateController { } return; } mRunningJobs.remove(jobStatus); if (!mChargeTracker.isCharging() && mRunningJobs.size() == 0) { if (mRunningBgJobs.remove(jobStatus) && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) { emitSessionLocked(sElapsedRealtimeClock.millis()); cancelCutoff(); } Loading @@ -870,13 +883,13 @@ public final class QuotaController extends StateController { } private void emitSessionLocked(long nowElapsed) { if (mJobCount <= 0) { if (mBgJobCount <= 0) { // Nothing to emit. return; } TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mJobCount); TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); saveTimingSession(mPkg.userId, mPkg.packageName, ts); mJobCount = 0; mBgJobCount = 0; // Don't reset the tracked jobs list as we need to keep tracking the current number // of jobs. // However, cancel the currently scheduled cutoff since it's not currently useful. Loading @@ -889,7 +902,7 @@ public final class QuotaController extends StateController { */ public boolean isActive() { synchronized (mLock) { return mJobCount > 0; return mBgJobCount > 0; } } Loading @@ -905,12 +918,12 @@ public final class QuotaController extends StateController { emitSessionLocked(nowElapsed); } else { // Start timing from unplug. if (mRunningJobs.size() > 0) { if (mRunningBgJobs.size() > 0) { mStartTimeElapsed = nowElapsed; // NOTE: this does have the unfortunate consequence that if the device is // repeatedly plugged in and unplugged, the job count for a package may be // artificially high. mJobCount = mRunningJobs.size(); mBgJobCount = mRunningBgJobs.size(); // Schedule cutoff since we're now actively tracking for quotas again. scheduleCutoff(); } Loading Loading @@ -958,12 +971,12 @@ public final class QuotaController extends StateController { pw.print("NOT active"); } pw.print(", "); pw.print(mJobCount); pw.print(" running jobs"); pw.print(mBgJobCount); pw.print(" running bg jobs"); pw.println(); pw.increaseIndent(); for (int i = 0; i < mRunningJobs.size(); i++) { JobStatus js = mRunningJobs.valueAt(i); for (int i = 0; i < mRunningBgJobs.size(); i++) { JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { pw.println(js.toShortString()); } Loading @@ -979,9 +992,9 @@ public final class QuotaController extends StateController { proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, mStartTimeElapsed); proto.write(StateControllerProto.QuotaController.Timer.JOB_COUNT, mJobCount); for (int i = 0; i < mRunningJobs.size(); i++) { JobStatus js = mRunningJobs.valueAt(i); proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); for (int i = 0; i < mRunningBgJobs.size(); i++) { JobStatus js = mRunningBgJobs.valueAt(i); if (predicate.test(js)) { js.writeToShortProto(proto, StateControllerProto.QuotaController.Timer.RUNNING_JOBS); Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +133 −0 Original line number Diff line number Diff line Loading @@ -780,6 +780,139 @@ public class QuotaControllerTest { assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ @Test public void testTimerTracking_AllBackground() { setDischarging(); JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); List<TimingSession> expected = new ArrayList<>(); // Test single job. long start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(5 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); // Test overlapping jobs. JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2); mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3); mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); advanceElapsedClock(SECOND_IN_MILLIS); start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobStatus2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobStatus3); advanceElapsedClock(20 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** Tests that Timers don't count foreground jobs. */ @Test public void testTimerTracking_AllForeground() { setDischarging(); JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1); jobStatus.uidActive = true; mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.prepareForExecutionLocked(jobStatus); advanceElapsedClock(5 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Tests that Timers properly track overlapping foreground and background jobs. */ @Test public void testTimerTracking_ForegroundAndBackground() { setDischarging(); JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1); JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2); JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3); jobFg3.uidActive = true; mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); List<TimingSession> expected = new ArrayList<>(); // UID starts out inactive. long start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobBg1); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); advanceElapsedClock(SECOND_IN_MILLIS); // Bg job starts while inactive, spans an entire active session, and ends after the // active session. // Fg job starts after the bg job and ends before the bg job. // Entire bg job duration should be counted since it started before active session. However, // count should only be 1 since Timer shouldn't count fg jobs. start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.prepareForExecutionLocked(jobBg2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobFg3); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); advanceElapsedClock(SECOND_IN_MILLIS); // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes // "inactive" and then bg job 2 starts. Then fg job ends. // This should result in two TimingSessions with a count of one each. start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); mQuotaController.prepareForExecutionLocked(jobBg1); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.prepareForExecutionLocked(jobFg3); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now start = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.prepareForExecutionLocked(jobBg2); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches * its quota. Loading