Loading core/proto/android/server/jobscheduler.proto +94 −1 Original line number Diff line number Diff line Loading @@ -215,6 +215,34 @@ message ConstantsProto { // The fraction of a prefetch job's running window that must pass before // we consider matching it against a metered network. optional double conn_prefetch_relax_frac = 22; // Whether to use heartbeats or rolling window for quota management. True // will use heartbeats, false will use a rolling window. optional bool use_heartbeats = 23; message QuotaController { // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; // How much time the package should have before transitioning from out-of-quota to in-quota. // This should not affect processing if the package is already in-quota. optional int64 in_quota_buffer_ms = 2; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 active_window_size_ms = 3; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 working_window_size_ms = 4; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 frequent_window_size_ms = 5; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 rare_window_size_ms = 6; } optional QuotaController quota_controller = 24; } message StateControllerProto { Loading Loading @@ -357,6 +385,65 @@ message StateControllerProto { } repeated TrackedJob tracked_jobs = 2; } message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional bool is_charging = 1; optional bool is_in_parole = 2; message TrackedJob { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional JobStatusDumpProto.Bucket effective_standby_bucket = 3; optional bool has_quota = 4; // The amount of time that this job has remaining in its quota. This // can be negative if the job is out of quota. optional int64 remaining_quota_ms = 5; } repeated TrackedJob tracked_jobs = 3; message Package { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 user_id = 1; optional string name = 2; } message TimingSession { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 start_time_elapsed = 1; optional int64 end_time_elapsed = 2; optional int32 job_count = 3; } message Timer { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional Package pkg = 1; // True if the Timer is actively tracking jobs. optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many are currently running. Valid only if the device is_active is true. optional int32 job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; } message PackageStats { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional Package pkg = 1; optional Timer timer = 2; repeated TimingSession saved_sessions = 3; } repeated PackageStats package_stats = 4; } message StorageController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -403,8 +490,10 @@ message StateControllerProto { ContentObserverController content_observer = 4; DeviceIdleJobsController device_idle = 5; IdleController idle = 6; QuotaController quota = 9; StorageController storage = 7; TimeController time = 8; // Next tag: 10 } } Loading Loading @@ -603,11 +692,13 @@ message JobStatusDumpProto { CONSTRAINT_CONNECTIVITY = 7; CONSTRAINT_CONTENT_TRIGGER = 8; CONSTRAINT_DEVICE_NOT_DOZING = 9; CONSTRAINT_WITHIN_QUOTA = 10; } repeated Constraint required_constraints = 7; repeated Constraint satisfied_constraints = 8; repeated Constraint unsatisfied_constraints = 9; optional bool is_doze_whitelisted = 10; optional bool is_uid_active = 26; message ImplicitConstraints { // The device isn't Dozing or this job will be in the foreground. This Loading @@ -627,6 +718,7 @@ message JobStatusDumpProto { TRACKING_IDLE = 3; TRACKING_STORAGE = 4; TRACKING_TIME = 5; TRACKING_QUOTA = 6; } // Controllers that are currently tracking the job. repeated TrackingController tracking_controllers = 11; Loading Loading @@ -660,6 +752,7 @@ message JobStatusDumpProto { NEVER = 4; } optional Bucket standby_bucket = 17; optional bool is_exempted_from_app_standby = 27; optional int64 enqueue_duration_ms = 18; // Can be negative if the earliest runtime deadline has passed. Loading @@ -674,5 +767,5 @@ message JobStatusDumpProto { optional int64 internal_flags = 24; // Next tag: 26 // Next tag: 28 } services/core/java/com/android/server/job/JobSchedulerService.java +190 −74 Original line number Diff line number Diff line Loading @@ -78,7 +78,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; Loading @@ -97,6 +96,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.QuotaController; import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StorageController; import com.android.server.job.controllers.TimeController; Loading Loading @@ -245,11 +245,11 @@ public class JobSchedulerService extends com.android.server.SystemService * Named indices into the STANDBY_BEATS array, for clarity in referring to * specific buckets' bookkeeping. */ static final int ACTIVE_INDEX = 0; static final int WORKING_INDEX = 1; static final int FREQUENT_INDEX = 2; static final int RARE_INDEX = 3; static final int NEVER_INDEX = 4; public static final int ACTIVE_INDEX = 0; public static final int WORKING_INDEX = 1; public static final int FREQUENT_INDEX = 2; public static final int RARE_INDEX = 3; public static final int NEVER_INDEX = 4; /** * Bookkeeping about when jobs last run. We keep our own record in heartbeat time, Loading Loading @@ -308,6 +308,10 @@ public class JobSchedulerService extends com.android.server.SystemService try { mConstants.updateConstantsLocked(Settings.Global.getString(mResolver, Settings.Global.JOB_SCHEDULER_CONSTANTS)); for (int controller = 0; controller < mControllers.size(); controller++) { final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. Loading @@ -315,10 +319,12 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (mConstants.USE_HEARTBEATS) { // Reset the heartbeat alarm based on the new heartbeat duration setNextHeartbeatAlarm(); } } } /** * All times are in milliseconds. These constants are kept synchronized with the system Loading Loading @@ -352,6 +358,19 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats"; 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_USE_HEARTBEATS = "use_heartbeats"; private static final String KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = "qc_allowed_time_per_period_ms"; private static final String KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = "qc_in_quota_buffer_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = "qc_window_size_active_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = "qc_window_size_working_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = "qc_window_size_frequent_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = "qc_window_size_rare_ms"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -377,6 +396,19 @@ public class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours 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 boolean DEFAULT_USE_HEARTBEATS = true; private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 10 * 60 * 1000L; // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 8 * 60 * 60 * 1000L; // 8 hours private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things Loading Loading @@ -495,6 +527,54 @@ public class JobSchedulerService extends com.android.server.SystemService * we consider matching it against a metered network. */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; /** * Whether to use heartbeats or rolling window for quota management. True will use * heartbeats, false will use a rolling window. */ public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS; /** How much time each app will have to run jobs within their standby bucket window. */ public long QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS; /** * How much time the package should have before transitioning from out-of-quota to in-quota. * This should not affect processing if the package is already in-quota. */ public long QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -567,6 +647,25 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_CONGESTION_DELAY_FRAC); CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS); QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS, DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS); QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS); QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS); QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS); QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); } void dump(IndentingPrintWriter pw) { Loading Loading @@ -600,6 +699,19 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println('}'); pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS, QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS, QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS, QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); pw.decreaseIndent(); } Loading Loading @@ -629,6 +741,23 @@ public class JobSchedulerService extends com.android.server.SystemService } proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS); final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS); proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS); proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS); proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS); proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); proto.end(qcToken); proto.end(token); } } Loading Loading @@ -1162,6 +1291,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(new ContentObserverController(this)); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mControllers.add(mDeviceIdleJobsController); mControllers.add(new QuotaController(this)); // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. Loading Loading @@ -1225,7 +1355,9 @@ public class JobSchedulerService extends com.android.server.SystemService mAppStateTracker = Preconditions.checkNotNull( LocalServices.getService(AppStateTracker.class)); if (mConstants.USE_HEARTBEATS) { setNextHeartbeatAlarm(); } // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); Loading Loading @@ -1869,6 +2001,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Intentionally does not touch the alarm timing void advanceHeartbeatLocked(long beatsElapsed) { if (!mConstants.USE_HEARTBEATS) { return; } mHeartbeat += beatsElapsed; if (DEBUG_STANDBY) { Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed Loading Loading @@ -1904,6 +2039,9 @@ public class JobSchedulerService extends com.android.server.SystemService void setNextHeartbeatAlarm() { final long heartbeatLength; synchronized (mLock) { if (!mConstants.USE_HEARTBEATS) { return; } heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME; } final long now = sElapsedRealtimeClock.millis(); Loading Loading @@ -1976,6 +2114,7 @@ public class JobSchedulerService extends com.android.server.SystemService return false; } if (mConstants.USE_HEARTBEATS) { // If the app is in a non-active standby bucket, make sure we've waited // an appropriate amount of time since the last invocation. During device- // wide parole, standby bucketing is ignored. Loading Loading @@ -2004,7 +2143,8 @@ public class JobSchedulerService extends com.android.server.SystemService if (bucket >= mConstants.STANDBY_BEATS.length || (mHeartbeat > appLastRan && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) { // TODO: log/trace that we're deferring the job due to bucketing if we hit this // TODO: log/trace that we're deferring the job due to bucketing if we // hit this if (job.getWhenStandbyDeferred() == 0) { if (DEBUG_STANDBY) { Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < " Loading @@ -2022,6 +2162,7 @@ public class JobSchedulerService extends com.android.server.SystemService } } } } // The expensive check last: validate that the defined package+service is // still present & viable. Loading Loading @@ -2364,32 +2505,7 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason) { final int uid = mLocalPM.getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); if (uid < 0) { if (DEBUG_STANDBY) { Slog.i(TAG, "App idle state change for unknown app " + packageName + "/" + userId); } return; } final int bucketIndex = standbyBucketToBucketIndex(bucket); // update job bookkeeping out of band BackgroundThread.getHandler().post(() -> { if (DEBUG_STANDBY) { Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex); } synchronized (mLock) { mJobs.forEachJobForSourceUid(uid, job -> { // double-check uid vs package name to disambiguate shared uids if (packageName.equals(job.getSourcePackageName())) { job.setStandbyBucket(bucketIndex); } }); onControllerStateChanged(); } }); // QuotaController handles this now. } @Override Loading Loading
core/proto/android/server/jobscheduler.proto +94 −1 Original line number Diff line number Diff line Loading @@ -215,6 +215,34 @@ message ConstantsProto { // The fraction of a prefetch job's running window that must pass before // we consider matching it against a metered network. optional double conn_prefetch_relax_frac = 22; // Whether to use heartbeats or rolling window for quota management. True // will use heartbeats, false will use a rolling window. optional bool use_heartbeats = 23; message QuotaController { // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; // How much time the package should have before transitioning from out-of-quota to in-quota. // This should not affect processing if the package is already in-quota. optional int64 in_quota_buffer_ms = 2; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 active_window_size_ms = 3; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 working_window_size_ms = 4; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 frequent_window_size_ms = 5; // The quota window size of the particular standby bucket. Apps in this standby bucket are // expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past // WINDOW_SIZE_MS. optional int64 rare_window_size_ms = 6; } optional QuotaController quota_controller = 24; } message StateControllerProto { Loading Loading @@ -357,6 +385,65 @@ message StateControllerProto { } repeated TrackedJob tracked_jobs = 2; } message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional bool is_charging = 1; optional bool is_in_parole = 2; message TrackedJob { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional JobStatusShortInfoProto info = 1; optional int32 source_uid = 2; optional JobStatusDumpProto.Bucket effective_standby_bucket = 3; optional bool has_quota = 4; // The amount of time that this job has remaining in its quota. This // can be negative if the job is out of quota. optional int64 remaining_quota_ms = 5; } repeated TrackedJob tracked_jobs = 3; message Package { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 user_id = 1; optional string name = 2; } message TimingSession { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int64 start_time_elapsed = 1; optional int64 end_time_elapsed = 2; optional int32 job_count = 3; } message Timer { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional Package pkg = 1; // True if the Timer is actively tracking jobs. optional bool is_active = 2; // The time this timer last became active. Only valid if is_active is true. optional int64 start_time_elapsed = 3; // How many are currently running. Valid only if the device is_active is true. optional int32 job_count = 4; // All of the jobs that the Timer is currently tracking. repeated JobStatusShortInfoProto running_jobs = 5; } message PackageStats { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional Package pkg = 1; optional Timer timer = 2; repeated TimingSession saved_sessions = 3; } repeated PackageStats package_stats = 4; } message StorageController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; Loading Loading @@ -403,8 +490,10 @@ message StateControllerProto { ContentObserverController content_observer = 4; DeviceIdleJobsController device_idle = 5; IdleController idle = 6; QuotaController quota = 9; StorageController storage = 7; TimeController time = 8; // Next tag: 10 } } Loading Loading @@ -603,11 +692,13 @@ message JobStatusDumpProto { CONSTRAINT_CONNECTIVITY = 7; CONSTRAINT_CONTENT_TRIGGER = 8; CONSTRAINT_DEVICE_NOT_DOZING = 9; CONSTRAINT_WITHIN_QUOTA = 10; } repeated Constraint required_constraints = 7; repeated Constraint satisfied_constraints = 8; repeated Constraint unsatisfied_constraints = 9; optional bool is_doze_whitelisted = 10; optional bool is_uid_active = 26; message ImplicitConstraints { // The device isn't Dozing or this job will be in the foreground. This Loading @@ -627,6 +718,7 @@ message JobStatusDumpProto { TRACKING_IDLE = 3; TRACKING_STORAGE = 4; TRACKING_TIME = 5; TRACKING_QUOTA = 6; } // Controllers that are currently tracking the job. repeated TrackingController tracking_controllers = 11; Loading Loading @@ -660,6 +752,7 @@ message JobStatusDumpProto { NEVER = 4; } optional Bucket standby_bucket = 17; optional bool is_exempted_from_app_standby = 27; optional int64 enqueue_duration_ms = 18; // Can be negative if the earliest runtime deadline has passed. Loading @@ -674,5 +767,5 @@ message JobStatusDumpProto { optional int64 internal_flags = 24; // Next tag: 26 // Next tag: 28 }
services/core/java/com/android/server/job/JobSchedulerService.java +190 −74 Original line number Diff line number Diff line Loading @@ -78,7 +78,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; Loading @@ -97,6 +96,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.QuotaController; import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StorageController; import com.android.server.job.controllers.TimeController; Loading Loading @@ -245,11 +245,11 @@ public class JobSchedulerService extends com.android.server.SystemService * Named indices into the STANDBY_BEATS array, for clarity in referring to * specific buckets' bookkeeping. */ static final int ACTIVE_INDEX = 0; static final int WORKING_INDEX = 1; static final int FREQUENT_INDEX = 2; static final int RARE_INDEX = 3; static final int NEVER_INDEX = 4; public static final int ACTIVE_INDEX = 0; public static final int WORKING_INDEX = 1; public static final int FREQUENT_INDEX = 2; public static final int RARE_INDEX = 3; public static final int NEVER_INDEX = 4; /** * Bookkeeping about when jobs last run. We keep our own record in heartbeat time, Loading Loading @@ -308,6 +308,10 @@ public class JobSchedulerService extends com.android.server.SystemService try { mConstants.updateConstantsLocked(Settings.Global.getString(mResolver, Settings.Global.JOB_SCHEDULER_CONSTANTS)); for (int controller = 0; controller < mControllers.size(); controller++) { final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. Loading @@ -315,10 +319,12 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (mConstants.USE_HEARTBEATS) { // Reset the heartbeat alarm based on the new heartbeat duration setNextHeartbeatAlarm(); } } } /** * All times are in milliseconds. These constants are kept synchronized with the system Loading Loading @@ -352,6 +358,19 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats"; 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_USE_HEARTBEATS = "use_heartbeats"; private static final String KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = "qc_allowed_time_per_period_ms"; private static final String KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = "qc_in_quota_buffer_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = "qc_window_size_active_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = "qc_window_size_working_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = "qc_window_size_frequent_ms"; private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = "qc_window_size_rare_ms"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -377,6 +396,19 @@ public class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours 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 boolean DEFAULT_USE_HEARTBEATS = true; private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 10 * 60 * 1000L; // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 8 * 60 * 60 * 1000L; // 8 hours private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things Loading Loading @@ -495,6 +527,54 @@ public class JobSchedulerService extends com.android.server.SystemService * we consider matching it against a metered network. */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; /** * Whether to use heartbeats or rolling window for quota management. True will use * heartbeats, false will use a rolling window. */ public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS; /** How much time each app will have to run jobs within their standby bucket window. */ public long QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS; /** * How much time the package should have before transitioning from out-of-quota to in-quota. * This should not affect processing if the package is already in-quota. */ public long QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past * WINDOW_SIZE_MS. */ public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -567,6 +647,25 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_CONGESTION_DELAY_FRAC); CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS); QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS, DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS); QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS); QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS); QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS); QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); } void dump(IndentingPrintWriter pw) { Loading Loading @@ -600,6 +699,19 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println('}'); pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS, QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS, QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS, QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); pw.decreaseIndent(); } Loading Loading @@ -629,6 +741,23 @@ public class JobSchedulerService extends com.android.server.SystemService } proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS); final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS); proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS); proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS); proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS); proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS); proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); proto.end(qcToken); proto.end(token); } } Loading Loading @@ -1162,6 +1291,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(new ContentObserverController(this)); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mControllers.add(mDeviceIdleJobsController); mControllers.add(new QuotaController(this)); // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. Loading Loading @@ -1225,7 +1355,9 @@ public class JobSchedulerService extends com.android.server.SystemService mAppStateTracker = Preconditions.checkNotNull( LocalServices.getService(AppStateTracker.class)); if (mConstants.USE_HEARTBEATS) { setNextHeartbeatAlarm(); } // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); Loading Loading @@ -1869,6 +2001,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Intentionally does not touch the alarm timing void advanceHeartbeatLocked(long beatsElapsed) { if (!mConstants.USE_HEARTBEATS) { return; } mHeartbeat += beatsElapsed; if (DEBUG_STANDBY) { Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed Loading Loading @@ -1904,6 +2039,9 @@ public class JobSchedulerService extends com.android.server.SystemService void setNextHeartbeatAlarm() { final long heartbeatLength; synchronized (mLock) { if (!mConstants.USE_HEARTBEATS) { return; } heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME; } final long now = sElapsedRealtimeClock.millis(); Loading Loading @@ -1976,6 +2114,7 @@ public class JobSchedulerService extends com.android.server.SystemService return false; } if (mConstants.USE_HEARTBEATS) { // If the app is in a non-active standby bucket, make sure we've waited // an appropriate amount of time since the last invocation. During device- // wide parole, standby bucketing is ignored. Loading Loading @@ -2004,7 +2143,8 @@ public class JobSchedulerService extends com.android.server.SystemService if (bucket >= mConstants.STANDBY_BEATS.length || (mHeartbeat > appLastRan && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) { // TODO: log/trace that we're deferring the job due to bucketing if we hit this // TODO: log/trace that we're deferring the job due to bucketing if we // hit this if (job.getWhenStandbyDeferred() == 0) { if (DEBUG_STANDBY) { Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < " Loading @@ -2022,6 +2162,7 @@ public class JobSchedulerService extends com.android.server.SystemService } } } } // The expensive check last: validate that the defined package+service is // still present & viable. Loading Loading @@ -2364,32 +2505,7 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason) { final int uid = mLocalPM.getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); if (uid < 0) { if (DEBUG_STANDBY) { Slog.i(TAG, "App idle state change for unknown app " + packageName + "/" + userId); } return; } final int bucketIndex = standbyBucketToBucketIndex(bucket); // update job bookkeeping out of band BackgroundThread.getHandler().post(() -> { if (DEBUG_STANDBY) { Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex); } synchronized (mLock) { mJobs.forEachJobForSourceUid(uid, job -> { // double-check uid vs package name to disambiguate shared uids if (packageName.equals(job.getSourcePackageName())) { job.setStandbyBucket(bucketIndex); } }); onControllerStateChanged(); } }); // QuotaController handles this now. } @Override Loading