Loading apex/jobscheduler/framework/java/android/app/job/JobParameters.java +9 −0 Original line number Diff line number Diff line Loading @@ -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 = { Loading @@ -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 { Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +38 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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: Loading Loading @@ -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"; Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -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 { Loading apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +15 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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"); } Loading apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java 0 → 100644 +303 −0 Original line number Diff line number Diff line /* * 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); } } core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
apex/jobscheduler/framework/java/android/app/job/JobParameters.java +9 −0 Original line number Diff line number Diff line Loading @@ -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 = { Loading @@ -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 { Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +38 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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: Loading Loading @@ -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"; Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -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 { Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +15 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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"); } Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java 0 → 100644 +303 −0 Original line number Diff line number Diff line /* * 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); } }
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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