Loading services/core/java/com/android/server/job/JobSchedulerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,11 @@ public interface JobSchedulerInternal { */ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket); /** * Tell the scheduler when a JobServiceContext starts running a job in an app */ void noteJobStart(String packageName, int userId); /** * Returns a list of pending jobs scheduled by the system service. */ Loading services/core/java/com/android/server/job/JobSchedulerService.java +71 −9 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; Loading Loading @@ -238,6 +239,27 @@ public final class JobSchedulerService extends com.android.server.SystemService long mHeartbeat = 0; long mLastHeartbeatTime = sElapsedRealtimeClock.millis(); /** * 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; /** * Bookkeeping about when jobs last run. We keep our own record in heartbeat time, * rather than rely on Usage Stats' timestamps, because heartbeat time can be * manipulated for testing purposes and we need job runnability to track that rather * than real time. * * Outer SparseArray slices by user handle; inner map of package name to heartbeat * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys * and it will be accessed in a known-hot code path. */ final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>(); static final String HEARTBEAT_TAG = "*job.heartbeat*"; final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener(); Loading Loading @@ -532,11 +554,11 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_EXP_BACKOFF_TIME); STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME, DEFAULT_STANDBY_HEARTBEAT_TIME); STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS, STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS, DEFAULT_STANDBY_WORKING_BEATS); STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS, STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS, DEFAULT_STANDBY_FREQUENT_BEATS); STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS, STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS, DEFAULT_STANDBY_RARE_BEATS); CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC, DEFAULT_CONN_CONGESTION_DELAY_FRAC); Loading Loading @@ -1420,15 +1442,40 @@ public final class JobSchedulerService extends com.android.server.SystemService periodicToReschedule.getLastFailedRunTime()); } /* * We default to "long enough ago that every bucket's jobs are immediately runnable" to * avoid starvation of apps in uncommon-use buckets that might arise from repeated * reboot behavior. */ long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) { final long heartbeat; final long timeSinceLastJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId); // The furthest back in pre-boot time that we need to bother with long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX]; boolean cacheHit = false; synchronized (mLock) { heartbeat = mHeartbeat - (timeSinceLastJob / mConstants.STANDBY_HEARTBEAT_TIME); HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId); if (jobPackages != null) { long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE); if (cachedValue < Long.MAX_VALUE) { cacheHit = true; heartbeat = cachedValue; } } if (!cacheHit) { // We haven't seen it yet; ask usage stats about it final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId); if (timeSinceJob < Long.MAX_VALUE) { // Usage stats knows about it from before, so calculate back from that // and go from there. heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME); } // If usage stats returned its "not found" MAX_VALUE, we still have the // negative default 'heartbeat' value we established above setLastJobHeartbeatLocked(packageName, userId, heartbeat); } } if (DEBUG_STANDBY) { Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId + " delta=" + timeSinceLastJob); Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId); } return heartbeat; } Loading @@ -1437,12 +1484,21 @@ public final class JobSchedulerService extends com.android.server.SystemService return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId()); } void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) { HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId); if (jobPackages == null) { jobPackages = new HashMap<>(); mLastJobHeartbeats.put(userId, jobPackages); } jobPackages.put(packageName, heartbeat); } // JobCompletedListener implementations. /** * A job just finished executing. We fetch the * {@link com.android.server.job.controllers.JobStatus} from the store and depending on * whether we want to reschedule we readd it to the controllers. * whether we want to reschedule we re-add it to the controllers. * @param jobStatus Completed job. * @param needsReschedule Whether the implementing class should reschedule this job. */ Loading Loading @@ -2208,6 +2264,12 @@ public final class JobSchedulerService extends com.android.server.SystemService return baseHeartbeat; } public void noteJobStart(String packageName, int userId) { synchronized (mLock) { setLastJobHeartbeatLocked(packageName, userId, mHeartbeat); } } /** * Returns a list of all pending jobs. A running job is not considered pending. Periodic * jobs are always considered pending. Loading services/core/java/com/android/server/job/JobServiceContext.java +6 −2 Original line number Diff line number Diff line Loading @@ -268,10 +268,14 @@ public final class JobServiceContext implements ServiceConnection { } catch (RemoteException e) { // Whatever. } final String jobPackage = job.getSourcePackageName(); final int jobUserId = job.getSourceUserId(); UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class); usageStats.setLastJobRunTime(job.getSourcePackageName(), job.getSourceUserId(), mExecutionStartTimeElapsed); usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed); JobSchedulerInternal jobScheduler = LocalServices.getService(JobSchedulerInternal.class); jobScheduler.noteJobStart(jobPackage, jobUserId); mAvailable = false; mStoppedReason = null; mStoppedTime = 0; Loading Loading
services/core/java/com/android/server/job/JobSchedulerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,11 @@ public interface JobSchedulerInternal { */ public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket); /** * Tell the scheduler when a JobServiceContext starts running a job in an app */ void noteJobStart(String packageName, int userId); /** * Returns a list of pending jobs scheduled by the system service. */ Loading
services/core/java/com/android/server/job/JobSchedulerService.java +71 −9 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; Loading Loading @@ -238,6 +239,27 @@ public final class JobSchedulerService extends com.android.server.SystemService long mHeartbeat = 0; long mLastHeartbeatTime = sElapsedRealtimeClock.millis(); /** * 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; /** * Bookkeeping about when jobs last run. We keep our own record in heartbeat time, * rather than rely on Usage Stats' timestamps, because heartbeat time can be * manipulated for testing purposes and we need job runnability to track that rather * than real time. * * Outer SparseArray slices by user handle; inner map of package name to heartbeat * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys * and it will be accessed in a known-hot code path. */ final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>(); static final String HEARTBEAT_TAG = "*job.heartbeat*"; final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener(); Loading Loading @@ -532,11 +554,11 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_EXP_BACKOFF_TIME); STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME, DEFAULT_STANDBY_HEARTBEAT_TIME); STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS, STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS, DEFAULT_STANDBY_WORKING_BEATS); STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS, STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS, DEFAULT_STANDBY_FREQUENT_BEATS); STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS, STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS, DEFAULT_STANDBY_RARE_BEATS); CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC, DEFAULT_CONN_CONGESTION_DELAY_FRAC); Loading Loading @@ -1420,15 +1442,40 @@ public final class JobSchedulerService extends com.android.server.SystemService periodicToReschedule.getLastFailedRunTime()); } /* * We default to "long enough ago that every bucket's jobs are immediately runnable" to * avoid starvation of apps in uncommon-use buckets that might arise from repeated * reboot behavior. */ long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) { final long heartbeat; final long timeSinceLastJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId); // The furthest back in pre-boot time that we need to bother with long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX]; boolean cacheHit = false; synchronized (mLock) { heartbeat = mHeartbeat - (timeSinceLastJob / mConstants.STANDBY_HEARTBEAT_TIME); HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId); if (jobPackages != null) { long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE); if (cachedValue < Long.MAX_VALUE) { cacheHit = true; heartbeat = cachedValue; } } if (!cacheHit) { // We haven't seen it yet; ask usage stats about it final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId); if (timeSinceJob < Long.MAX_VALUE) { // Usage stats knows about it from before, so calculate back from that // and go from there. heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME); } // If usage stats returned its "not found" MAX_VALUE, we still have the // negative default 'heartbeat' value we established above setLastJobHeartbeatLocked(packageName, userId, heartbeat); } } if (DEBUG_STANDBY) { Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId + " delta=" + timeSinceLastJob); Slog.v(TAG, "Last job heartbeat " + heartbeat + " for " + packageName + "/" + userId); } return heartbeat; } Loading @@ -1437,12 +1484,21 @@ public final class JobSchedulerService extends com.android.server.SystemService return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId()); } void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) { HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId); if (jobPackages == null) { jobPackages = new HashMap<>(); mLastJobHeartbeats.put(userId, jobPackages); } jobPackages.put(packageName, heartbeat); } // JobCompletedListener implementations. /** * A job just finished executing. We fetch the * {@link com.android.server.job.controllers.JobStatus} from the store and depending on * whether we want to reschedule we readd it to the controllers. * whether we want to reschedule we re-add it to the controllers. * @param jobStatus Completed job. * @param needsReschedule Whether the implementing class should reschedule this job. */ Loading Loading @@ -2208,6 +2264,12 @@ public final class JobSchedulerService extends com.android.server.SystemService return baseHeartbeat; } public void noteJobStart(String packageName, int userId) { synchronized (mLock) { setLastJobHeartbeatLocked(packageName, userId, mHeartbeat); } } /** * Returns a list of all pending jobs. A running job is not considered pending. Periodic * jobs are always considered pending. Loading
services/core/java/com/android/server/job/JobServiceContext.java +6 −2 Original line number Diff line number Diff line Loading @@ -268,10 +268,14 @@ public final class JobServiceContext implements ServiceConnection { } catch (RemoteException e) { // Whatever. } final String jobPackage = job.getSourcePackageName(); final int jobUserId = job.getSourceUserId(); UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class); usageStats.setLastJobRunTime(job.getSourcePackageName(), job.getSourceUserId(), mExecutionStartTimeElapsed); usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed); JobSchedulerInternal jobScheduler = LocalServices.getService(JobSchedulerInternal.class); jobScheduler.noteJobStart(jobPackage, jobUserId); mAvailable = false; mStoppedReason = null; mStoppedTime = 0; Loading