Merge "Add basic prefetch job handling."
This commit is contained in:
committed by
Android (Google) Code Review
commit
260119c181
@@ -235,6 +235,14 @@ public class JobParameters implements Parcelable {
|
||||
public static final int STOP_REASON_USER = 13;
|
||||
/** The system is doing some processing that requires stopping this job. */
|
||||
public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
|
||||
/**
|
||||
* The system's estimate of when the app will be launched changed significantly enough to
|
||||
* decide this job shouldn't be running right now. This will mostly apply to prefetch jobs.
|
||||
*
|
||||
* @see JobInfo#isPrefetch()
|
||||
* @see JobInfo.Builder#setPrefetch(boolean)
|
||||
*/
|
||||
public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = {"STOP_REASON_"}, value = {
|
||||
@@ -253,6 +261,7 @@ public class JobParameters implements Parcelable {
|
||||
STOP_REASON_APP_STANDBY,
|
||||
STOP_REASON_USER,
|
||||
STOP_REASON_SYSTEM_PROCESSING,
|
||||
STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface StopReason {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.job;
|
||||
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
@@ -105,6 +106,7 @@ import com.android.server.job.controllers.ContentObserverController;
|
||||
import com.android.server.job.controllers.DeviceIdleJobsController;
|
||||
import com.android.server.job.controllers.IdleController;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
import com.android.server.job.controllers.PrefetchController;
|
||||
import com.android.server.job.controllers.QuotaController;
|
||||
import com.android.server.job.controllers.RestrictingController;
|
||||
import com.android.server.job.controllers.StateController;
|
||||
@@ -252,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private final StorageController mStorageController;
|
||||
/** Need directly for sending uid state changes */
|
||||
private final DeviceIdleJobsController mDeviceIdleJobsController;
|
||||
/** Needed to get next estimated launch time. */
|
||||
private final PrefetchController mPrefetchController;
|
||||
/** Needed to get remaining quota time. */
|
||||
private final QuotaController mQuotaController;
|
||||
/** Needed to get max execution time and expedited-job allowance. */
|
||||
@@ -427,6 +431,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
|
||||
mConstants.updateConnectivityConstantsLocked();
|
||||
break;
|
||||
case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
|
||||
mConstants.updatePrefetchConstantsLocked();
|
||||
break;
|
||||
case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
|
||||
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
|
||||
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
|
||||
@@ -482,6 +489,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
|
||||
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
|
||||
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
|
||||
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
|
||||
"prefetch_force_batch_relax_threshold_ms";
|
||||
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";
|
||||
@@ -503,6 +512,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
|
||||
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
|
||||
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
|
||||
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
|
||||
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
|
||||
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
|
||||
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
|
||||
@@ -556,6 +566,14 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*/
|
||||
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
|
||||
|
||||
/**
|
||||
* The amount of time within which we would consider the app to be launching relatively soon
|
||||
* and will relax the force batching policy on prefetch jobs. If the app is not going to be
|
||||
* launched within this amount of time from now, then we will force batch the prefetch job.
|
||||
*/
|
||||
public long PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
|
||||
DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
|
||||
|
||||
/**
|
||||
* Whether to enable quota limits on APIs.
|
||||
*/
|
||||
@@ -635,6 +653,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
|
||||
}
|
||||
|
||||
private void updatePrefetchConstantsLocked() {
|
||||
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
|
||||
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
|
||||
DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
|
||||
}
|
||||
|
||||
private void updateApiQuotaConstantsLocked() {
|
||||
ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
|
||||
@@ -700,6 +725,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
|
||||
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
|
||||
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
|
||||
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
|
||||
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
|
||||
|
||||
pw.print(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
|
||||
pw.print(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
|
||||
@@ -1577,6 +1604,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mControllers.add(new ContentObserverController(this));
|
||||
mDeviceIdleJobsController = new DeviceIdleJobsController(this);
|
||||
mControllers.add(mDeviceIdleJobsController);
|
||||
mPrefetchController = new PrefetchController(this);
|
||||
mControllers.add(mPrefetchController);
|
||||
mQuotaController =
|
||||
new QuotaController(this, backgroundJobsController, connectivityController);
|
||||
mControllers.add(mQuotaController);
|
||||
@@ -2333,6 +2362,15 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
} else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
|
||||
// Restricted jobs must always be batched
|
||||
shouldForceBatchJob = true;
|
||||
} else if (job.getJob().isPrefetch()) {
|
||||
// Only relax batching on prefetch jobs if we expect the app to be launched
|
||||
// relatively soon. PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS defines what
|
||||
// "relatively soon" means.
|
||||
final long relativelySoonCutoffTime = sSystemClock.millis()
|
||||
+ mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
|
||||
shouldForceBatchJob =
|
||||
mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
|
||||
> relativelySoonCutoffTime;
|
||||
} else if (job.getNumFailures() > 0) {
|
||||
shouldForceBatchJob = false;
|
||||
} else {
|
||||
|
||||
@@ -93,6 +93,7 @@ public final class JobStatus {
|
||||
static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
|
||||
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
|
||||
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
|
||||
static final int CONSTRAINT_PREFETCH = 1 << 23;
|
||||
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
|
||||
|
||||
// The following set of dynamic constraints are for specific use cases (as explained in their
|
||||
@@ -147,6 +148,7 @@ public final class JobStatus {
|
||||
private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
|
||||
| CONSTRAINT_DEADLINE
|
||||
| CONSTRAINT_IDLE
|
||||
| CONSTRAINT_PREFETCH
|
||||
| CONSTRAINT_TARE_WEALTH
|
||||
| CONSTRAINT_TIMING_DELAY
|
||||
| CONSTRAINT_WITHIN_QUOTA;
|
||||
@@ -1175,6 +1177,11 @@ public final class JobStatus {
|
||||
return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, nowElapsed, state);
|
||||
}
|
||||
|
||||
/** @return true if the constraint was changed, false otherwise. */
|
||||
boolean setPrefetchConstraintSatisfied(final long nowElapsed, boolean state) {
|
||||
return setConstraintSatisfied(CONSTRAINT_PREFETCH, nowElapsed, state);
|
||||
}
|
||||
|
||||
/** @return true if the constraint was changed, false otherwise. */
|
||||
boolean setTimingDelayConstraintSatisfied(final long nowElapsed, boolean state) {
|
||||
return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, nowElapsed, state);
|
||||
@@ -1387,6 +1394,9 @@ public final class JobStatus {
|
||||
case CONSTRAINT_DEVICE_NOT_DOZING:
|
||||
return JobParameters.STOP_REASON_DEVICE_STATE;
|
||||
|
||||
case CONSTRAINT_PREFETCH:
|
||||
return JobParameters.STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED;
|
||||
|
||||
case CONSTRAINT_TARE_WEALTH:
|
||||
case CONSTRAINT_WITHIN_QUOTA:
|
||||
return JobParameters.STOP_REASON_QUOTA;
|
||||
@@ -1581,12 +1591,12 @@ public final class JobStatus {
|
||||
/** All constraints besides implicit and deadline. */
|
||||
static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
|
||||
| CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
|
||||
| CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
|
||||
| CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER | CONSTRAINT_PREFETCH;
|
||||
|
||||
// Soft override covers all non-"functional" constraints
|
||||
static final int SOFT_OVERRIDE_CONSTRAINTS =
|
||||
CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
|
||||
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
|
||||
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE | CONSTRAINT_PREFETCH;
|
||||
|
||||
/** Returns true whenever all dynamically set constraints are satisfied. */
|
||||
public boolean areDynamicConstraintsSatisfied() {
|
||||
@@ -1775,6 +1785,9 @@ public final class JobStatus {
|
||||
if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
|
||||
pw.print(" BACKGROUND_NOT_RESTRICTED");
|
||||
}
|
||||
if ((constraints & CONSTRAINT_PREFETCH) != 0) {
|
||||
pw.print(" PREFETCH");
|
||||
}
|
||||
if ((constraints & CONSTRAINT_TARE_WEALTH) != 0) {
|
||||
pw.print(" TARE_WEALTH");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.job.controllers;
|
||||
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
import static com.android.server.job.JobSchedulerService.sSystemClock;
|
||||
|
||||
import android.annotation.CurrentTimeMillisLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArrayMap;
|
||||
import android.util.TimeUtils;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.JobSchedulerBackgroundThread;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Controller to delay prefetch jobs until we get close to an expected app launch.
|
||||
*/
|
||||
public class PrefetchController extends StateController {
|
||||
private static final String TAG = "JobScheduler.Prefetch";
|
||||
private static final boolean DEBUG = JobSchedulerService.DEBUG
|
||||
|| Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final PcConstants mPcConstants;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
|
||||
/**
|
||||
* Cached set of the estimated next launch times of each app. Time are in the current time
|
||||
* millis ({@link CurrentTimeMillisLong}) timebase.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
|
||||
|
||||
/**
|
||||
* The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
|
||||
* to launch within this amount of time into the future, then we will let a prefetch job run.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@CurrentTimeMillisLong
|
||||
private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
|
||||
|
||||
public PrefetchController(JobSchedulerService service) {
|
||||
super(service);
|
||||
mPcConstants = new PcConstants();
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
|
||||
if (jobStatus.getJob().isPrefetch()) {
|
||||
final int userId = jobStatus.getSourceUserId();
|
||||
final String pkgName = jobStatus.getSourcePackageName();
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs == null) {
|
||||
jobs = new ArraySet<>();
|
||||
mTrackedJobs.add(userId, pkgName, jobs);
|
||||
}
|
||||
jobs.add(jobStatus);
|
||||
updateConstraintLocked(jobStatus,
|
||||
sSystemClock.millis(), sElapsedRealtimeClock.millis());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
|
||||
boolean forUpdate) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
|
||||
jobStatus.getSourcePackageName());
|
||||
if (jobs != null) {
|
||||
jobs.remove(jobStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void onAppRemovedLocked(String packageName, int uid) {
|
||||
if (packageName == null) {
|
||||
Slog.wtf(TAG, "Told app removed but given null package name.");
|
||||
return;
|
||||
}
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
mTrackedJobs.delete(userId, packageName);
|
||||
mEstimatedLaunchTimes.delete(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void onUserRemovedLocked(int userId) {
|
||||
mTrackedJobs.delete(userId);
|
||||
mEstimatedLaunchTimes.delete(userId);
|
||||
}
|
||||
|
||||
/** Return the app's next estimated launch time. */
|
||||
@GuardedBy("mLock")
|
||||
@CurrentTimeMillisLong
|
||||
public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
|
||||
final int userId = jobStatus.getSourceUserId();
|
||||
final String pkgName = jobStatus.getSourcePackageName();
|
||||
Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
|
||||
final long now = sSystemClock.millis();
|
||||
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
|
||||
// TODO(194532703): get estimated time from UsageStats
|
||||
nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
|
||||
mEstimatedLaunchTimes.add(userId, pkgName, nextEstimatedLaunchTime);
|
||||
}
|
||||
return nextEstimatedLaunchTime;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
|
||||
String pkgName) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs == null) {
|
||||
return false;
|
||||
}
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < jobs.size(); i++) {
|
||||
final JobStatus js = jobs.valueAt(i);
|
||||
changed |= updateConstraintLocked(js, now, nowElapsed);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
|
||||
long nowElapsed) {
|
||||
return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
|
||||
getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void prepareForUpdatedConstantsLocked() {
|
||||
mPcConstants.mShouldReevaluateConstraints = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void processConstantLocked(DeviceConfig.Properties properties, String key) {
|
||||
mPcConstants.processConstantLocked(properties, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void onConstantsUpdatedLocked() {
|
||||
if (mPcConstants.mShouldReevaluateConstraints) {
|
||||
// Update job bookkeeping out of band.
|
||||
JobSchedulerBackgroundThread.getHandler().post(() -> {
|
||||
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
|
||||
synchronized (mLock) {
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
final long now = sSystemClock.millis();
|
||||
for (int u = 0; u < mTrackedJobs.numMaps(); ++u) {
|
||||
final int userId = mTrackedJobs.keyAt(u);
|
||||
for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
|
||||
final String packageName = mTrackedJobs.keyAt(u, p);
|
||||
if (maybeUpdateConstraintForPkgLocked(
|
||||
now, nowElapsed, userId, packageName)) {
|
||||
changedJobs.addAll(mTrackedJobs.valueAt(u, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changedJobs.size() > 0) {
|
||||
mStateChangedListener.onControllerStateChanged(changedJobs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class PcConstants {
|
||||
private boolean mShouldReevaluateConstraints = false;
|
||||
|
||||
/** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
|
||||
private static final String PC_CONSTANT_PREFIX = "pc_";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String KEY_LAUNCH_TIME_THRESHOLD_MS =
|
||||
PC_CONSTANT_PREFIX + "launch_time_threshold_ms";
|
||||
|
||||
private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS;
|
||||
|
||||
/** How much time each app will have to run jobs within their standby bucket window. */
|
||||
public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
|
||||
@NonNull String key) {
|
||||
switch (key) {
|
||||
case KEY_LAUNCH_TIME_THRESHOLD_MS:
|
||||
LAUNCH_TIME_THRESHOLD_MS =
|
||||
properties.getLong(key, DEFAULT_LAUNCH_TIME_THRESHOLD_MS);
|
||||
// Limit the threshold to the range [1, 24] hours.
|
||||
long newLaunchTimeThresholdMs = Math.min(24 * HOUR_IN_MILLIS,
|
||||
Math.max(HOUR_IN_MILLIS, LAUNCH_TIME_THRESHOLD_MS));
|
||||
if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
|
||||
mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void dump(IndentingPrintWriter pw) {
|
||||
pw.println();
|
||||
pw.print(PrefetchController.class.getSimpleName());
|
||||
pw.println(":");
|
||||
pw.increaseIndent();
|
||||
|
||||
pw.print(KEY_LAUNCH_TIME_THRESHOLD_MS, LAUNCH_TIME_THRESHOLD_MS).println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////// TESTING HELPERS /////////////////////////////
|
||||
|
||||
@VisibleForTesting
|
||||
long getLaunchTimeThresholdMs() {
|
||||
return mLaunchTimeThresholdMs;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
PcConstants getPcConstants() {
|
||||
return mPcConstants;
|
||||
}
|
||||
|
||||
//////////////////////////// DATA DUMP //////////////////////////////
|
||||
|
||||
@Override
|
||||
@GuardedBy("mLock")
|
||||
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
|
||||
final long now = sSystemClock.millis();
|
||||
|
||||
pw.println("Cached launch times:");
|
||||
pw.increaseIndent();
|
||||
for (int u = 0; u < mEstimatedLaunchTimes.numMaps(); ++u) {
|
||||
final int userId = mEstimatedLaunchTimes.keyAt(u);
|
||||
for (int p = 0; p < mEstimatedLaunchTimes.numElementsForKey(userId); ++p) {
|
||||
final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
|
||||
final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);
|
||||
|
||||
pw.print("<" + userId + ">" + pkgName + ": ");
|
||||
pw.print(estimatedLaunchTime);
|
||||
pw.print(" (");
|
||||
TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
|
||||
TimeUtils.HUNDRED_DAY_FIELD_LEN);
|
||||
pw.println(" from now)");
|
||||
}
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println();
|
||||
mTrackedJobs.forEach((jobs) -> {
|
||||
for (int j = 0; j < jobs.size(); j++) {
|
||||
final JobStatus js = jobs.valueAt(j);
|
||||
if (!predicate.test(js)) {
|
||||
continue;
|
||||
}
|
||||
pw.print("#");
|
||||
js.printUniqueId(pw);
|
||||
pw.print(" from ");
|
||||
UserHandle.formatUid(pw, js.getSourceUid());
|
||||
pw.println();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpConstants(IndentingPrintWriter pw) {
|
||||
mPcConstants.dump(pw);
|
||||
}
|
||||
}
|
||||
@@ -8055,6 +8055,7 @@ package android.app.job {
|
||||
field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
|
||||
field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
|
||||
field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
|
||||
field public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; // 0xf
|
||||
field public static final int STOP_REASON_PREEMPT = 2; // 0x2
|
||||
field public static final int STOP_REASON_QUOTA = 10; // 0xa
|
||||
field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.job.controllers;
|
||||
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.DeviceConfig;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.controllers.PrefetchController.PcConstants;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoSession;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PrefetchControllerTest {
|
||||
private PrefetchController mPrefetchController;
|
||||
private PcConstants mPcConstants;
|
||||
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
|
||||
|
||||
private MockitoSession mMockingSession;
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private JobSchedulerService mJobSchedulerService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMockingSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.spyStatic(DeviceConfig.class)
|
||||
.mockStatic(LocalServices.class)
|
||||
.startMocking();
|
||||
|
||||
// Called in StateController constructor.
|
||||
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
||||
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
||||
// Used in PrefetchController.PcConstants
|
||||
doAnswer((Answer<Void>) invocationOnMock -> null)
|
||||
.when(() -> DeviceConfig.addOnPropertiesChangedListener(
|
||||
anyString(), any(Executor.class),
|
||||
any(DeviceConfig.OnPropertiesChangedListener.class)));
|
||||
mDeviceConfigPropertiesBuilder =
|
||||
new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
|
||||
doAnswer(
|
||||
(Answer<DeviceConfig.Properties>) invocationOnMock
|
||||
-> mDeviceConfigPropertiesBuilder.build())
|
||||
.when(() -> DeviceConfig.getProperties(
|
||||
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
|
||||
|
||||
// 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
|
||||
// causes sessions with negative timestamps, they will fail.
|
||||
JobSchedulerService.sSystemClock =
|
||||
getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
|
||||
24 * HOUR_IN_MILLIS);
|
||||
JobSchedulerService.sUptimeMillisClock = getShiftedClock(
|
||||
Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
|
||||
24 * HOUR_IN_MILLIS);
|
||||
JobSchedulerService.sElapsedRealtimeClock = getShiftedClock(
|
||||
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
|
||||
24 * HOUR_IN_MILLIS);
|
||||
|
||||
// Initialize real objects.
|
||||
// Capture the listeners.
|
||||
mPrefetchController = new PrefetchController(mJobSchedulerService);
|
||||
mPcConstants = mPrefetchController.getPcConstants();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mMockingSession != null) {
|
||||
mMockingSession.finishMocking();
|
||||
}
|
||||
}
|
||||
|
||||
private Clock getShiftedClock(Clock clock, long incrementMs) {
|
||||
return Clock.offset(clock, Duration.ofMillis(incrementMs));
|
||||
}
|
||||
|
||||
private void setDeviceConfigLong(String key, long val) {
|
||||
mDeviceConfigPropertiesBuilder.setLong(key, val);
|
||||
synchronized (mPrefetchController.mLock) {
|
||||
mPrefetchController.prepareForUpdatedConstantsLocked();
|
||||
mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstantsUpdating_ValidValues() {
|
||||
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 5 * HOUR_IN_MILLIS);
|
||||
|
||||
assertEquals(5 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstantsUpdating_InvalidValues() {
|
||||
// Test negatives/too low.
|
||||
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 4 * MINUTE_IN_MILLIS);
|
||||
|
||||
assertEquals(HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
|
||||
|
||||
// Test larger than a day. Controller should cap at one day.
|
||||
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 25 * HOUR_IN_MILLIS);
|
||||
|
||||
assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user