Merge "Add app-level timeout & ANR limits." into udc-dev am: d2e06de805

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23452747

Change-Id: I7201cfadf7ed8cc1d55f8a1efaa6bd4d304957f1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2023-06-08 14:50:20 +00:00
committed by Automerger Merge Worker
10 changed files with 1000 additions and 23 deletions

View File

@@ -59,6 +59,10 @@ public interface JobSchedulerInternal {
*/
void reportAppUsage(String packageName, int userId);
/** @return {@code true} if the app is considered buggy from JobScheduler's perspective. */
boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
int timeoutBlameUserId, @NonNull String timeoutBlamePackageName);
/**
* @return {@code true} if the given notification is associated with any user-initiated jobs.
*/

View File

@@ -322,16 +322,25 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
".schedulePersisted out-of-quota logged";
private static final String QUOTA_TRACKER_TIMEOUT_UIJ_TAG = "timeout-uij";
private static final String QUOTA_TRACKER_TIMEOUT_EJ_TAG = "timeout-ej";
private static final String QUOTA_TRACKER_TIMEOUT_REG_TAG = "timeout-reg";
private static final String QUOTA_TRACKER_TIMEOUT_TOTAL_TAG = "timeout-total";
private static final String QUOTA_TRACKER_ANR_TAG = "anr";
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category(
".schedulePersisted()");
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category(
".schedulePersisted out-of-quota logged");
private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> {
if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED;
}
return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED;
};
private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ =
new Category(QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ =
new Category(QUOTA_TRACKER_TIMEOUT_EJ_TAG);
private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_REG =
new Category(QUOTA_TRACKER_TIMEOUT_REG_TAG);
private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL =
new Category(QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
private static final Category QUOTA_TRACKER_CATEGORY_ANR = new Category(QUOTA_TRACKER_ANR_TAG);
private static final Category QUOTA_TRACKER_CATEGORY_DISABLED = new Category("disabled");
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -493,10 +502,18 @@ public class JobSchedulerService extends com.android.server.SystemService
}
switch (name) {
case Constants.KEY_ENABLE_API_QUOTAS:
case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
case Constants.KEY_API_QUOTA_SCHEDULE_COUNT:
case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS:
case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT:
case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT:
case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS:
if (!apiQuotaScheduleUpdated) {
mConstants.updateApiQuotaConstantsLocked();
updateQuotaTracker();
@@ -583,10 +600,26 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void updateQuotaTracker() {
mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
mQuotaTracker.setEnabled(
mConstants.ENABLE_API_QUOTAS || mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
mConstants.API_QUOTA_SCHEDULE_COUNT,
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_REG,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_ANR,
mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
/**
@@ -616,6 +649,8 @@ public class JobSchedulerService extends com.android.server.SystemService
"conn_low_signal_strength_relax_frac";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
// This has been enabled for 3+ full releases. We're unlikely to disable it.
// TODO(141645789): remove this flag
private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -623,6 +658,22 @@ public class JobSchedulerService extends com.android.server.SystemService
"aq_schedule_throw_exception";
private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
"aq_schedule_return_failure";
private static final String KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC =
"enable_execution_safeguards_udc";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
"es_u_timeout_uij_count";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
"es_u_timeout_ej_count";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
"es_u_timeout_reg_count";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
"es_u_timeout_total_count";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
"es_u_timeout_window_ms";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT =
"es_u_anr_count";
private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
"es_u_anr_window_ms";
private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
"runtime_free_quota_max_limit_ms";
@@ -662,6 +713,17 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
private static final boolean DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
// EJs have a shorter timeout, so set a higher limit for them to start with.
private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 5;
private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 3;
private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = 10;
private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
24 * HOUR_IN_MILLIS;
private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = 3;
private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
6 * HOUR_IN_MILLIS;
@VisibleForTesting
public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
@VisibleForTesting
@@ -774,6 +836,55 @@ public class JobSchedulerService extends com.android.server.SystemService
public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
/**
* Whether to enable the execution safeguards added in UDC.
*/
public boolean ENABLE_EXECUTION_SAFEGUARDS_UDC = DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC;
/**
* The maximum number of times an app can have a user-iniated job time out before the system
* begins removing some of the app's privileges.
*/
public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT;
/**
* The maximum number of times an app can have an expedited job time out before the system
* begins removing some of the app's privileges.
*/
public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT;
/**
* The maximum number of times an app can have a regular job time out before the system
* begins removing some of the app's privileges.
*/
public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT;
/**
* The maximum number of times an app can have jobs time out before the system
* attempts to restrict most of the app's privileges.
*/
public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT;
/**
* The time window that {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT},
* {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT},
* {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT}, and
* {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT} should be evaluated over.
*/
public long EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS;
/**
* The maximum number of times an app can ANR from JobScheduler's perspective before
* JobScheduler will attempt to restrict the app.
*/
public int EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT;
/**
* The time window that {@link #EXECUTION_SAFEGUARDS_UDC_ANR_COUNT}
* should be evaluated over.
*/
public long EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS;
/** The maximum amount of time we will let a job run for when quota is "free". */
public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
@@ -915,6 +1026,9 @@ public class JobSchedulerService extends com.android.server.SystemService
private void updateApiQuotaConstantsLocked() {
ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
ENABLE_EXECUTION_SAFEGUARDS_UDC = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC);
// Set a minimum value on the quota limit so it's not so low that it interferes with
// legitimate use cases.
API_QUOTA_SCHEDULE_COUNT = Math.max(250,
@@ -931,6 +1045,40 @@ public class JobSchedulerService extends com.android.server.SystemService
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
// Set a minimum value on the timeout limit so it's not so low that it interferes with
// legitimate use cases.
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = Math.max(2,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT));
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = Math.max(2,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT));
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = Math.max(2,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
final int highestTimeoutCount = Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = Math.max(highestTimeoutCount,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT));
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = Math.max(1,
DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT));
EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
private void updateRuntimeConstantsLocked() {
@@ -1029,6 +1177,23 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
pw.print(KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, ENABLE_EXECUTION_SAFEGUARDS_UDC)
.println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
EXECUTION_SAFEGUARDS_UDC_ANR_COUNT).println();
pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS).println();
pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
@@ -2252,12 +2417,52 @@ public class JobSchedulerService extends com.android.server.SystemService
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
mConstants.API_QUOTA_SCHEDULE_COUNT,
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
final Categorizer quotaCategorizer = (userId, packageName, tag) -> {
if (QUOTA_TRACKER_TIMEOUT_UIJ_TAG.equals(tag)) {
return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
? QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_TIMEOUT_EJ_TAG.equals(tag)) {
return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
? QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_TIMEOUT_REG_TAG.equals(tag)) {
return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
? QUOTA_TRACKER_CATEGORY_TIMEOUT_REG
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_TIMEOUT_TOTAL_TAG.equals(tag)) {
return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
? QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_ANR_TAG.equals(tag)) {
return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
? QUOTA_TRACKER_CATEGORY_ANR
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
return mConstants.ENABLE_API_QUOTAS
? QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
if (QUOTA_TRACKER_SCHEDULE_LOGGED.equals(tag)) {
return mConstants.ENABLE_API_QUOTAS
? QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED
: QUOTA_TRACKER_CATEGORY_DISABLED;
}
Slog.wtf(TAG, "Unexpected category tag: " + tag);
return QUOTA_TRACKER_CATEGORY_DISABLED;
};
mQuotaTracker = new CountQuotaTracker(context, quotaCategorizer);
updateQuotaTracker();
// Log at most once per minute.
// Set outside updateQuotaTracker() since this is intentionally not configurable.
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_DISABLED, Integer.MAX_VALUE, 60_000);
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
mAppStandbyInternal.addListener(mStandbyTracker);
@@ -2762,6 +2967,48 @@ public class JobSchedulerService extends com.android.server.SystemService
0 /* Reset cumulativeExecutionTime because of successful execution */);
}
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
// If madeActive = 0, the job never actually started.
if (!jobTimedOut && jobStatus.madeActive > 0) {
final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
// The debug reason may be different if we stopped the job for some other reason
// (eg. constraints), so look at total execution time to be safe.
if (jobStatus.startedAsUserInitiatedJob) {
// TODO: factor in different min guarantees for different UI job types
jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
} else if (jobStatus.startedAsExpeditedJob) {
jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
} else {
jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_GUARANTEE_MS;
}
}
if (jobTimedOut) {
final int userId = jobStatus.getTimeoutBlameUserId();
final String pkg = jobStatus.getTimeoutBlamePackageName();
mQuotaTracker.noteEvent(userId, pkg,
jobStatus.startedAsUserInitiatedJob
? QUOTA_TRACKER_TIMEOUT_UIJ_TAG
: (jobStatus.startedAsExpeditedJob
? QUOTA_TRACKER_TIMEOUT_EJ_TAG
: QUOTA_TRACKER_TIMEOUT_REG_TAG));
if (!mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_TIMEOUT_TOTAL_TAG)) {
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
}
}
if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_ANR) {
final int callingUserId = jobStatus.getUserId();
final String callingPkg = jobStatus.getServiceComponent().getPackageName();
if (!mQuotaTracker.noteEvent(callingUserId, callingPkg, QUOTA_TRACKER_ANR_TAG)) {
mAppStandbyInternal.restrictApp(callingPkg, callingUserId,
UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
}
}
}
// JobCompletedListener implementations.
/**
@@ -2784,6 +3031,8 @@ public class JobSchedulerService extends com.android.server.SystemService
mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;
maybeProcessBuggyJob(jobStatus, debugStopReason);
if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_UNINSTALL
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED) {
// The job should have already been cleared from the rest of the JS tracking. No need
@@ -3511,26 +3760,36 @@ public class JobSchedulerService extends com.android.server.SystemService
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName())) {
// The calling package is the one doing the work, so use it in the
// timeout quota checks.
final boolean isWithinTimeoutQuota = mQuotaTracker.isWithinQuota(
job.getTimeoutBlameUserId(), job.getTimeoutBlamePackageName(),
QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
final long upperLimitMs = isWithinTimeoutQuota
? mConstants.RUNTIME_UI_LIMIT_MS
: mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
if (job.getJob().getRequiredNetwork() != null) {
// User-initiated data transfers.
if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
return Math.min(upperLimitMs,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
}
// Try to give the job at least as much time as we think the transfer
// will take, but cap it at the maximum limit.
final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
* mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
return Math.min(upperLimitMs,
Math.max(factoredTransferTimeMs,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
return Math.min(upperLimitMs,
Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
return Math.min(upperLimitMs, mConstants.RUNTIME_MIN_UI_GUARANTEE_MS);
} else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3547,13 +3806,24 @@ public class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName())) {
job.getSourceUid(), job.getSourcePackageName())
&& mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
job.getTimeoutBlamePackageName(),
QUOTA_TRACKER_TIMEOUT_UIJ_TAG)) {
return mConstants.RUNTIME_UI_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
// Only let the app use the higher runtime if it hasn't repeatedly timed out.
final String timeoutTag = job.shouldTreatAsExpeditedJob()
? QUOTA_TRACKER_TIMEOUT_EJ_TAG : QUOTA_TRACKER_TIMEOUT_REG_TAG;
final long upperLimitMs =
mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
job.getTimeoutBlamePackageName(), timeoutTag)
? mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS
: mConstants.RUNTIME_MIN_GUARANTEE_MS;
return Math.min(upperLimitMs,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
: mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -3796,6 +4066,17 @@ public class JobSchedulerService extends com.android.server.SystemService
JobSchedulerService.this.reportAppUsage(packageName, userId);
}
@Override
public boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
int timeoutBlameUserId, @NonNull String timeoutBlamePackageName) {
return !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
QUOTA_TRACKER_ANR_TAG)
|| !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)
|| !mQuotaTracker.isWithinQuota(timeoutBlameUserId, timeoutBlamePackageName,
QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
}
@Override
public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
int userId, @NonNull String packageName) {

View File

@@ -1088,13 +1088,77 @@ public final class JobStatus {
return UserHandle.getUserId(callingUid);
}
private boolean shouldBlameSourceForTimeout() {
// If the system scheduled the job on behalf of an app, assume the app is the one
// doing the work and blame the app directly. This is the case with things like
// syncs via SyncManager.
// If the system didn't schedule the job on behalf of an app, then
// blame the app doing the actual work. Proxied jobs are a little tricky.
// Proxied jobs scheduled by built-in system apps like DownloadManager may be fine
// and we could consider exempting those jobs. For example, in DownloadManager's
// case, all it does is download files and the code is vetted. A timeout likely
// means it's downloading a large file, which isn't an error. For now, DownloadManager
// is an exempted app, so this shouldn't be an issue.
// However, proxied jobs coming from other system apps (such as those that can
// be updated separately from an OTA) may not be fine and we would want to apply
// this policy to those jobs/apps.
// TODO(284512488): consider exempting DownloadManager or other system apps
return UserHandle.isCore(callingUid);
}
/**
* Returns the package name that should most likely be blamed for the job timing out.
*/
public String getTimeoutBlamePackageName() {
if (shouldBlameSourceForTimeout()) {
return sourcePackageName;
}
return getServiceComponent().getPackageName();
}
/**
* Returns the UID that should most likely be blamed for the job timing out.
*/
public int getTimeoutBlameUid() {
if (shouldBlameSourceForTimeout()) {
return sourceUid;
}
return callingUid;
}
/**
* Returns the userId that should most likely be blamed for the job timing out.
*/
public int getTimeoutBlameUserId() {
if (shouldBlameSourceForTimeout()) {
return sourceUserId;
}
return UserHandle.getUserId(callingUid);
}
/**
* Returns an appropriate standby bucket for the job, taking into account any standby
* exemptions.
*/
public int getEffectiveStandbyBucket() {
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
final boolean isBuggy = jsi.isAppConsideredBuggy(
getUserId(), getServiceComponent().getPackageName(),
getTimeoutBlameUserId(), getTimeoutBlamePackageName());
final int actualBucket = getStandbyBucket();
if (actualBucket == EXEMPTED_INDEX) {
// EXEMPTED apps always have their jobs exempted, even if they're buggy, because the
// user has explicitly told the system to avoid restricting the app for power reasons.
if (isBuggy) {
final String pkg;
if (getServiceComponent().getPackageName().equals(sourcePackageName)) {
pkg = sourcePackageName;
} else {
pkg = getServiceComponent().getPackageName() + "/" + sourcePackageName;
}
Slog.w(TAG, "Exempted app " + pkg + " considered buggy");
}
return actualBucket;
}
if (uidActive || getJob().isExemptedFromAppStandby()) {
@@ -1102,13 +1166,18 @@ public final class JobStatus {
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
// If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
// (potentially because it's used frequently by the user), limit its effective bucket
// so that it doesn't get to run as much as a normal ACTIVE app.
final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
&& mHasMediaBackupExemption) {
// Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
// Treat it as if it's at least WORKING_INDEX since media backup jobs are important
// to the user, and the
// source package may not have been used directly in a while.
return Math.min(WORKING_INDEX, actualBucket);
return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
}
return actualBucket;
return Math.max(highestBucket, actualBucket);
}
/** Returns the real standby bucket of the job. */

View File

@@ -773,6 +773,14 @@ public final class QuotaController extends StateController {
// If quota is currently "free", then the job can run for the full amount of time,
// regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
if (mService.isBatteryCharging()
// The top and foreground cases here were added because apps in those states
// aren't really restricted and the work could be something the user is
// waiting for. Now that user-initiated jobs are a defined concept, we may
// not need these exemptions as much. However, UIJs are currently limited
// (as of UDC) to data transfer work. There may be other work that could
// rely on this exception. Once we add more UIJ types, we can re-evaluate
// the need for these exceptions.
// TODO: re-evaluate the need for these exceptions
|| mTopAppCache.get(jobStatus.getSourceUid())
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {

View File

@@ -3025,7 +3025,7 @@ public class AppStandbyController
public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 30 * ONE_MINUTE;
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
COMPRESS_TIME ? ONE_MINUTE : ONE_HOUR;
public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
2 * ONE_MINUTE;

View File

@@ -28,6 +28,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -148,6 +149,9 @@ public class JobSchedulerServiceTest {
// Used in JobConcurrencyManager.
doReturn(mock(UserManagerInternal.class))
.when(() -> LocalServices.getService(UserManagerInternal.class));
// Used in JobStatus.
doReturn(mock(JobSchedulerInternal.class))
.when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Called via IdleController constructor.
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -168,6 +172,8 @@ public class JobSchedulerServiceTest {
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
// Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
@@ -312,6 +318,260 @@ public class JobSchedulerServiceTest {
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
}
@Test
public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(1)
.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(2).setExpedited(true));
JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(3));
spyOn(jobUij);
when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
jobUij.startedAsUserInitiatedJob = true;
spyOn(jobEj);
when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
jobEj.startedAsExpeditedJob = true;
mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
// Safeguards disabled -> no penalties.
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 UIJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 UIJ timeouts. Safeguards disabled -> no penalties.
jobUij.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 EJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 EJ timeouts. Safeguards disabled -> no penalties.
jobEj.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 reg timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 Reg timeouts. Safeguards disabled -> no penalties.
jobReg.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
}
@Test
public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(1)
.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(2).setExpedited(true));
JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
createJobInfo(3));
spyOn(jobUij);
when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
jobUij.startedAsUserInitiatedJob = true;
spyOn(jobEj);
when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
jobEj.startedAsExpeditedJob = true;
mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
// No timeouts -> no penalties.
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 UIJ timeout. No execution penalty yet.
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// Not a timeout -> 1 UIJ timeout. No execution penalty yet.
jobUij.madeActive = sUptimeMillisClock.millis() - 1;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 UIJ timeouts. Min execution penalty only for UIJs.
jobUij.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 EJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 EJ timeouts. Max execution penalty for EJs.
jobEj.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 1 reg timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
// 2 Reg timeouts. Max execution penalty for regular jobs.
jobReg.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobReg));
}
@Test
public void testGetMaxJobExecutionTimeMs() {
JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
@@ -327,7 +587,7 @@ public class JobSchedulerServiceTest {
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
.when(tareController).getMaxJobExecutionTimeMsLocked(any());
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
@@ -337,6 +597,306 @@ public class JobSchedulerServiceTest {
mService.getMaxJobExecutionTimeMs(jobUIDT));
}
@Test
public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(1)
.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(2).setExpedited(true));
JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(3));
spyOn(jobUij);
when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
jobUij.startedAsUserInitiatedJob = true;
spyOn(jobEj);
when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
jobEj.startedAsExpeditedJob = true;
QuotaController quotaController = mService.getQuotaController();
spyOn(quotaController);
TareController tareController = mService.getTareController();
spyOn(tareController);
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(tareController).getMaxJobExecutionTimeMsLocked(any());
mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
// Safeguards disabled -> no penalties.
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 UIJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 UIJ timeouts. Safeguards disabled -> no penalties.
jobUij.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 EJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 EJ timeouts. Safeguards disabled -> no penalties.
jobEj.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 reg timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 Reg timeouts. Safeguards disabled -> no penalties.
jobReg.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
}
@Test
public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(1)
.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(2).setExpedited(true));
JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
createJobInfo(3));
spyOn(jobUij);
when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
jobUij.startedAsUserInitiatedJob = true;
spyOn(jobEj);
when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
jobEj.startedAsExpeditedJob = true;
QuotaController quotaController = mService.getQuotaController();
spyOn(quotaController);
TareController tareController = mService.getTareController();
spyOn(tareController);
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(tareController).getMaxJobExecutionTimeMsLocked(any());
mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
// No timeouts -> no penalties.
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 UIJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
jobUij.madeActive = sUptimeMillisClock.millis() - 1;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 UIJ timeouts. Max execution penalty only for UIJs.
jobUij.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 EJ timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// Not a timeout -> 1 EJ timeout. No max execution penalty yet.
jobEj.madeActive = sUptimeMillisClock.millis() - 1;
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 EJ timeouts. Max execution penalty for EJs.
jobEj.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 1 reg timeout. No max execution penalty yet.
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// Not a timeout -> 1 reg timeout. No max execution penalty yet.
jobReg.madeActive = sUptimeMillisClock.millis() - 1;
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
// 2 Reg timeouts. Max execution penalty for regular jobs.
jobReg.madeActive =
sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
grantRunUserInitiatedJobsPermission(false);
assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
mService.getMaxJobExecutionTimeMs(jobUij));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMaxJobExecutionTimeMs(jobEj));
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMaxJobExecutionTimeMs(jobReg));
}
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
@@ -1226,6 +1786,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1249,6 +1810,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1270,6 +1832,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1292,6 +1855,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1315,6 +1879,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(false).build();
final JobWorkItem item = new JobWorkItem.Builder().build();
@@ -1337,6 +1902,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
final JobWorkItem item = new JobWorkItem.Builder().build();

View File

@@ -73,6 +73,7 @@ import android.telephony.TelephonyManager;
import android.util.DataUnit;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -124,6 +125,10 @@ public class ConnectivityControllerTest {
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
// Used in JobStatus.
LocalServices.removeServiceForTest(JobSchedulerInternal.class);
LocalServices.addService(JobSchedulerInternal.class, mock(JobSchedulerInternal.class));
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);

View File

@@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -45,6 +46,8 @@ import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
@@ -581,6 +584,40 @@ public class JobStatusTest {
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@Test
public void testGetEffectiveStandbyBucket_buggyApp() {
when(mJobSchedulerInternal.isAppConsideredBuggy(
anyInt(), anyString(), anyInt(), anyString()))
.thenReturn(true);
final JobInfo jobInfo = new JobInfo.Builder(1234, TEST_JOB_COMPONENT).build();
JobStatus job = createJobStatus(jobInfo);
// Exempt apps be exempting.
job.setStandbyBucket(EXEMPTED_INDEX);
assertEquals(EXEMPTED_INDEX, job.getEffectiveStandbyBucket());
// Actual bucket is higher than the buggy cap, so the cap comes into effect.
job.setStandbyBucket(ACTIVE_INDEX);
assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
// Buckets at the cap or below shouldn't be affected.
job.setStandbyBucket(WORKING_INDEX);
assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
job.setStandbyBucket(FREQUENT_INDEX);
assertEquals(FREQUENT_INDEX, job.getEffectiveStandbyBucket());
job.setStandbyBucket(RARE_INDEX);
assertEquals(RARE_INDEX, job.getEffectiveStandbyBucket());
job.setStandbyBucket(RESTRICTED_INDEX);
assertEquals(RESTRICTED_INDEX, job.getEffectiveStandbyBucket());
job.setStandbyBucket(NEVER_INDEX);
assertEquals(NEVER_INDEX, job.getEffectiveStandbyBucket());
}
@Test
public void testModifyingInternalFlags() {
final JobInfo jobInfo =

View File

@@ -58,6 +58,7 @@ import android.util.SparseArray;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.PrefetchController.PcConstants;
@@ -135,6 +136,9 @@ public class PrefetchControllerTest {
when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
.thenAnswer(invocationOnMock
-> mPackagesForUid.get(invocationOnMock.getArgument(0)));
// Used in JobStatus.
doReturn(mock(JobSchedulerInternal.class))
.when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and PrefetchController sometimes floors values at 0, so if the test time

View File

@@ -85,6 +85,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
@@ -190,6 +191,8 @@ public class QuotaControllerTest {
doReturn(mPowerAllowlistInternal)
.when(() -> LocalServices.getService(PowerAllowlistInternal.class));
// Used in JobStatus.
doReturn(mock(JobSchedulerInternal.class))
.when(() -> LocalServices.getService(JobSchedulerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Used in QuotaController.Handler.