Merge "Don't give RESTRICTED jobs temp allowlist exemption." into tm-dev

This commit is contained in:
Kweku Adams
2022-04-05 15:42:56 +00:00
committed by Android (Google) Code Review
2 changed files with 165 additions and 9 deletions

View File

@@ -307,7 +307,7 @@ public final class QuotaController extends StateController {
private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
/**
* Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
* Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed
* realtime timebase).
*/
private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
@@ -815,6 +815,19 @@ public final class QuotaController extends StateController {
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
}
private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket,
long nowElapsed) {
if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) {
// Don't let RESTRICTED apps get free quota from the temp allowlist.
// TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows
// them to start FGS
return false;
}
final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid);
return mTempAllowlistCache.get(sourceUid)
|| nowElapsed < tempAllowlistGracePeriodEndElapsed;
}
/** @return true if the job is within expedited job quota. */
@GuardedBy("mLock")
public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
@@ -833,11 +846,8 @@ public final class QuotaController extends StateController {
}
final long nowElapsed = sElapsedRealtimeClock.millis();
final long tempAllowlistGracePeriodEndElapsed =
mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
|| nowElapsed < tempAllowlistGracePeriodEndElapsed;
if (hasTempAllowlistExemption) {
if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(),
jobStatus.getEffectiveStandbyBucket(), nowElapsed)) {
return true;
}
@@ -2127,10 +2137,8 @@ public final class QuotaController extends StateController {
final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
mPkg.userId, nowElapsed);
final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
final boolean hasTempAllowlistExemption = !mRegularJobTimer
&& (mTempAllowlistCache.get(mUid)
|| nowElapsed < tempAllowlistGracePeriodEndElapsed);
&& hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed);
final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
final boolean hasTopAppExemption = !mRegularJobTimer
&& (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);

View File

@@ -2164,6 +2164,49 @@ public class QuotaControllerTest {
}
}
@Test
public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
setDischarging();
JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
setStandbyBucket(RESTRICTED_INDEX, js);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
}
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
// The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
// out of quota.
mTempAllowlistListener.onAppAdded(mSourceUid);
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mTempAllowlistListener.onAppRemoved(mSourceUid);
advanceElapsedClock(10 * SECOND_IN_MILLIS);
// Still in grace period
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
}
advanceElapsedClock(6 * SECOND_IN_MILLIS);
// Out of grace period.
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
}
}
/**
* Tests that Timers properly track sessions when an app becomes top and is closed.
*/
@@ -5559,6 +5602,111 @@ public class QuotaControllerTest {
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
@Test
public void testEJTimerTracking_TempAllowlisting_Restricted() {
setDischarging();
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
setStandbyBucket(RESTRICTED_INDEX, job);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStartTrackingJobLocked(job, null);
}
assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
List<TimingSession> expected = new ArrayList<>();
long start = JobSchedulerService.sElapsedRealtimeClock.millis();
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(job);
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(job, job, true);
}
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
// Job starts after app is added to temp allowlist and stops before removal.
start = JobSchedulerService.sElapsedRealtimeClock.millis();
mTempAllowlistListener.onAppAdded(mSourceUid);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStartTrackingJobLocked(job, null);
mQuotaController.prepareForExecutionLocked(job);
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(job, null, false);
}
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
// Job starts after app is added to temp allowlist and stops after removal,
// before grace period ends.
start = JobSchedulerService.sElapsedRealtimeClock.millis();
mTempAllowlistListener.onAppAdded(mSourceUid);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStartTrackingJobLocked(job, null);
mQuotaController.prepareForExecutionLocked(job);
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
mTempAllowlistListener.onAppRemoved(mSourceUid);
long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
advanceElapsedClock(elapsedGracePeriodMs);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(job, null, false);
}
expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
advanceElapsedClock(SECOND_IN_MILLIS);
elapsedGracePeriodMs += SECOND_IN_MILLIS;
// Job starts during grace period and ends after grace period ends
start = JobSchedulerService.sElapsedRealtimeClock.millis();
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStartTrackingJobLocked(job, null);
mQuotaController.prepareForExecutionLocked(job);
}
final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
advanceElapsedClock(remainingGracePeriod);
// Wait for handler to update Timer
// Can't directly evaluate the message because for some reason, the captured message returns
// the wrong 'what' even though the correct message goes to the handler and the correct
// path executes.
verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(job, job, true);
}
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
// Job starts and runs completely after temp allowlist grace period.
advanceElapsedClock(10 * SECOND_IN_MILLIS);
start = JobSchedulerService.sElapsedRealtimeClock.millis();
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStartTrackingJobLocked(job, null);
mQuotaController.prepareForExecutionLocked(job);
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(job, job, true);
}
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
assertEquals(expected,
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
/**
* Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
*/