diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 2ae437e74b5a6..c561a1908b213 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -596,6 +596,22 @@ public class AlarmManager { null, workSource, null); } + /** + * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}. + * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener. + *

+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be + * invoked via the specified target Handler, or on the application's main looper + * if {@code null} is passed as the {@code targetHandler} parameter. + * + * @hide + */ + public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis, + String tag, OnAlarmListener listener, Handler targetHandler, WorkSource workSource) { + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag, + targetHandler, workSource, null); + } + /** * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}. * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener. diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 61790ea92ed9d..63c1dbb99d824 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -192,7 +192,8 @@ public class JobInfo implements Parcelable { private final int flags; /** - * Unique job id associated with this class. This is assigned to your job by the scheduler. + * Unique job id associated with this application (uid). This is the same job ID + * you supplied in the {@link Builder} constructor. */ public int getId() { return jobId; @@ -524,9 +525,9 @@ public class JobInfo implements Parcelable { /** Builder class for constructing {@link JobInfo} objects. */ public static final class Builder { - private int mJobId; + private final int mJobId; + private final ComponentName mJobService; private PersistableBundle mExtras = PersistableBundle.EMPTY; - private ComponentName mJobService; private int mPriority = PRIORITY_DEFAULT; private int mFlags; // Requirements. @@ -553,11 +554,15 @@ public class JobInfo implements Parcelable { private boolean mBackoffPolicySet = false; /** + * Initialize a new Builder to construct a {@link JobInfo}. + * * @param jobId Application-provided id for this job. Subsequent calls to cancel, or - * jobs created with the same jobId, will update the pre-existing job with - * the same id. + * jobs created with the same jobId, will update the pre-existing job with + * the same id. This ID must be unique across all clients of the same uid + * (not just the same package). You will want to make sure this is a stable + * id across app updates, so probably not based on a resource ID. * @param jobService The endpoint that you implement that will receive the callback from the - * JobScheduler. + * JobScheduler. */ public Builder(int jobId, ComponentName jobService) { mJobService = jobService; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f867fb0246867..e408cca4e330d 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8017,6 +8017,36 @@ public final class Settings { */ public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants"; + /** + * Job scheduler specific settings. + * This is encoded as a key=value list, separated by commas. Ex: + * + * "min_ready_jobs_count=2,moderate_use_factor=.5" + * + * The following keys are supported: + * + *

+         * min_idle_count                       (int)
+         * min_charging_count                   (int)
+         * min_connectivity_count               (int)
+         * min_content_count                    (int)
+         * min_ready_jobs_count                 (int)
+         * heavy_use_factor                     (float)
+         * moderate_use_factor                  (float)
+         * fg_job_count                         (int)
+         * bg_normal_job_count                  (int)
+         * bg_moderate_job_count                (int)
+         * bg_low_job_count                     (int)
+         * bg_critical_job_count                (int)
+         * 
+ * + *

+ * Type: string + * @hide + * @see com.android.server.job.JobSchedulerService.Constants + */ + public static final String JOB_SCHEDULER_CONSTANTS = "job_scheduler_constants"; + /** * ShortcutManager specific settings. * This is encoded as a key=value list, separated by commas. Ex: diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java index 4abdde00e6c69..e4c025dece49c 100644 --- a/core/java/android/util/KeyValueListParser.java +++ b/core/java/android/util/KeyValueListParser.java @@ -62,6 +62,24 @@ public class KeyValueListParser { } } + /** + * Get the value for key as an int. + * @param key The key to lookup. + * @param def The value to return if the key was not found, or the value was not a long. + * @return the int value associated with the key. + */ + public int getInt(String key, int def) { + String value = mValues.get(key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + // fallthrough + } + } + return def; + } + /** * Get the value for key as a long. * @param key The key to lookup. diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java index eb5edd38d8a91..ba96b7494bba5 100644 --- a/services/core/java/com/android/server/job/JobPackageTracker.java +++ b/services/core/java/com/android/server/job/JobPackageTracker.java @@ -105,6 +105,8 @@ public final class JobPackageTracker { final long mStartElapsedTime; final long mStartClockTime; long mSummedTime; + int mMaxTotalActive; + int mMaxFgActive; public DataSet(DataSet otherTimes) { mStartUptimeTime = otherTimes.mStartUptimeTime; @@ -257,6 +259,12 @@ public final class JobPackageTracker { } } } + if (mMaxTotalActive > out.mMaxTotalActive) { + out.mMaxTotalActive = mMaxTotalActive; + } + if (mMaxFgActive > out.mMaxFgActive) { + out.mMaxFgActive = mMaxFgActive; + } } void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) { @@ -317,6 +325,9 @@ public final class JobPackageTracker { pw.println(); } } + pw.print(prefix); pw.print(" Max concurrency: "); + pw.print(mMaxTotalActive); pw.print(" total, "); + pw.print(mMaxFgActive); pw.println(" foreground"); } } @@ -366,6 +377,15 @@ public final class JobPackageTracker { addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName()); } + public void noteConcurrency(int totalActive, int fgActive) { + if (totalActive > mCurDataSet.mMaxTotalActive) { + mCurDataSet.mMaxTotalActive = totalActive; + } + if (fgActive > mCurDataSet.mMaxFgActive) { + mCurDataSet.mMaxFgActive = fgActive; + } + } + public float getLoadFactor(JobStatus job) { final int uid = job.getSourceUid(); final String pkg = job.getSourcePackageName(); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 1b8eccb431f54..5e212b0ef6631 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -23,6 +23,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -37,6 +39,7 @@ import android.app.job.JobService; import android.app.job.IJobScheduler; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -44,6 +47,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; import android.net.Uri; import android.os.BatteryStats; import android.os.Binder; @@ -57,6 +61,8 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.Settings; +import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -98,17 +104,12 @@ public final class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG = false; /** The maximum number of concurrent jobs we run at one time. */ - private static final int MAX_JOB_CONTEXTS_COUNT = 12; - /** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */ - private static final int FG_JOB_CONTEXTS_COUNT = 4; + private static final int MAX_JOB_CONTEXTS_COUNT = 16; /** Enforce a per-app limit on scheduled jobs? */ private static final boolean ENFORCE_MAX_JOBS = true; /** The maximum number of jobs that we allow an unprivileged app to schedule */ private static final int MAX_JOBS_PER_APP = 100; - /** This is the job execution factor that is considered to be heavy use of the system. */ - private static final float HEAVY_USE_FACTOR = .9f; - /** This is the job execution factor that is considered to be moderate use of the system. */ - private static final float MODERATE_USE_FACTOR = .5f; + /** Global local for all job scheduler state. */ final Object mLock = new Object(); @@ -122,34 +123,6 @@ public final class JobSchedulerService extends com.android.server.SystemService static final int MSG_STOP_JOB = 2; static final int MSG_CHECK_JOB_GREEDY = 3; - // Policy constants - /** - * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things - * early. - */ - static final int MIN_IDLE_COUNT = 1; - /** - * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things - * early. - */ - static final int MIN_CHARGING_COUNT = 1; - /** - * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule - * things early. - */ - static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready. - /** - * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule - * things early. - */ - static final int MIN_CONTENT_COUNT = 1; - /** - * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running - * some work early. - * This is correlated with the amount of batching we'll be able to do. - */ - static final int MIN_READY_JOBS_COUNT = 2; - /** * Track Services that have currently active or pending jobs. The index is provided by * {@link JobStatus#getServiceToken()} @@ -186,7 +159,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * Current limit on the number of concurrent JobServiceContext entries we want to * keep actively running a job. */ - int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; + int mMaxActiveJobs = 1; /** * Which uids are currently in the foreground. @@ -211,6 +184,211 @@ public final class JobSchedulerService extends com.android.server.SystemService */ int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + /** + * All times are in milliseconds. These constants are kept synchronized with the system + * global Settings. Any access to this class or its fields should be done while + * holding the JobSchedulerService.mLock lock. + */ + private final class Constants extends ContentObserver { + // Key names stored in the settings value. + private static final String KEY_MIN_IDLE_COUNT = "min_idle_count"; + private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count"; + private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count"; + private static final String KEY_MIN_CONTENT_COUNT = "min_content_count"; + private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count"; + private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor"; + private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor"; + private static final String KEY_FG_JOB_COUNT = "fg_job_count"; + private static final String KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count"; + private static final String KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count"; + private static final String KEY_BG_LOW_JOB_COUNT = "bg_low_job_count"; + private static final String KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count"; + + private static final int DEFAULT_MIN_IDLE_COUNT = 1; + private static final int DEFAULT_MIN_CHARGING_COUNT = 1; + private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1; + private static final int DEFAULT_MIN_CONTENT_COUNT = 1; + private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1; + private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; + private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; + private static final int DEFAULT_FG_JOB_COUNT = 4; + private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6; + private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4; + private static final int DEFAULT_BG_LOW_JOB_COUNT = 2; + private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1; + + /** + * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things + * early. + */ + int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT; + /** + * Minimum # of charging jobs that must be ready in order to force the JMS to schedule + * things early. + */ + int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT; + /** + * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule + * things early. 1 == Run connectivity jobs as soon as ready. + */ + int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT; + /** + * Minimum # of content trigger jobs that must be ready in order to force the JMS to + * schedule things early. + */ + int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT; + /** + * Minimum # of jobs (with no particular constraints) for which the JMS will be happy + * running some work early. This (and thus the other min counts) is now set to 1, to + * prevent any batching at this level. Since we now do batching through doze, that is + * a much better mechanism. + */ + int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT; + /** + * This is the job execution factor that is considered to be heavy use of the system. + */ + float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR; + /** + * This is the job execution factor that is considered to be moderate use of the system. + */ + float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR; + /** + * The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. + */ + int FG_JOB_COUNT = DEFAULT_FG_JOB_COUNT; + /** + * The maximum number of background jobs we allow when the system is in a normal + * memory state. + */ + int BG_NORMAL_JOB_COUNT = DEFAULT_BG_NORMAL_JOB_COUNT; + /** + * The maximum number of background jobs we allow when the system is in a moderate + * memory state. + */ + int BG_MODERATE_JOB_COUNT = DEFAULT_BG_MODERATE_JOB_COUNT; + /** + * The maximum number of background jobs we allow when the system is in a low + * memory state. + */ + int BG_LOW_JOB_COUNT = DEFAULT_BG_LOW_JOB_COUNT; + /** + * The maximum number of background jobs we allow when the system is in a critical + * memory state. + */ + int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT; + + private ContentResolver mResolver; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + public Constants(Handler handler) { + super(handler); + } + + public void start(ContentResolver resolver) { + mResolver = resolver; + mResolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this); + updateConstants(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updateConstants(); + } + + private void updateConstants() { + synchronized (mLock) { + try { + mParser.setString(Settings.Global.getString(mResolver, + Settings.Global.ALARM_MANAGER_CONSTANTS)); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on + // with defaults. + Slog.e(TAG, "Bad device idle settings", e); + } + + MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT, + DEFAULT_MIN_IDLE_COUNT); + MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT, + DEFAULT_MIN_CHARGING_COUNT); + MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT, + DEFAULT_MIN_CONNECTIVITY_COUNT); + MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT, + DEFAULT_MIN_CONTENT_COUNT); + MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT, + DEFAULT_MIN_READY_JOBS_COUNT); + HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR, + DEFAULT_HEAVY_USE_FACTOR); + MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR, + DEFAULT_MODERATE_USE_FACTOR); + FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT, + DEFAULT_FG_JOB_COUNT); + BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT, + DEFAULT_BG_NORMAL_JOB_COUNT); + if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) { + BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT; + } + BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT, + DEFAULT_BG_MODERATE_JOB_COUNT); + if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) { + BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT; + } + BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT, + DEFAULT_BG_LOW_JOB_COUNT); + if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) { + BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT; + } + BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT, + DEFAULT_BG_CRITICAL_JOB_COUNT); + if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) { + BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT; + } + } + } + + void dump(PrintWriter pw) { + pw.println(" Settings:"); + + pw.print(" "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("="); + pw.print(MIN_IDLE_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("="); + pw.print(MIN_CHARGING_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("="); + pw.print(MIN_CONNECTIVITY_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("="); + pw.print(MIN_CONTENT_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("="); + pw.print(MIN_READY_JOBS_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("="); + pw.print(HEAVY_USE_FACTOR); pw.println(); + + pw.print(" "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("="); + pw.print(MODERATE_USE_FACTOR); pw.println(); + + pw.print(" "); pw.print(KEY_FG_JOB_COUNT); pw.print("="); + pw.print(FG_JOB_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("="); + pw.print(BG_NORMAL_JOB_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("="); + pw.print(BG_MODERATE_JOB_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("="); + pw.print(BG_LOW_JOB_COUNT); pw.println(); + + pw.print(" "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("="); + pw.print(BG_CRITICAL_JOB_COUNT); pw.println(); + } + } + + final Constants mConstants; + /** * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we * still clean up. On reinstall the package will have a new uid. @@ -550,6 +728,7 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(DeviceIdleJobsController.get(this)); mHandler = new JobHandler(context.getMainLooper()); + mConstants = new Constants(mHandler); mJobSchedulerStub = new JobSchedulerStub(); mJobs = JobStore.initAndGet(this); } @@ -563,6 +742,7 @@ public final class JobSchedulerService extends com.android.server.SystemService @Override public void onBootPhase(int phase) { if (PHASE_SYSTEM_SERVICES_READY == phase) { + mConstants.start(getContext().getContentResolver()); // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -994,11 +1174,12 @@ public final class JobSchedulerService extends com.android.server.SystemService public void postProcess() { if (backoffCount > 0 || - idleCount >= MIN_IDLE_COUNT || - connectivityCount >= MIN_CONNECTIVITY_COUNT || - chargingCount >= MIN_CHARGING_COUNT || - contentCount >= MIN_CONTENT_COUNT || - (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) { + idleCount >= mConstants.MIN_IDLE_COUNT || + connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT || + chargingCount >= mConstants.MIN_CHARGING_COUNT || + contentCount >= mConstants.MIN_CONTENT_COUNT || + (runnableJobs != null + && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) { if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); } @@ -1095,9 +1276,9 @@ public final class JobSchedulerService extends com.android.server.SystemService private int adjustJobPriority(int curPriority, JobStatus job) { if (curPriority < JobInfo.PRIORITY_TOP_APP) { float factor = mJobPackageTracker.getLoadFactor(job); - if (factor >= HEAVY_USE_FACTOR) { + if (factor >= mConstants.HEAVY_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING; - } else if (factor >= MODERATE_USE_FACTOR) { + } else if (factor >= mConstants.MODERATE_USE_FACTOR) { curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING; } } @@ -1135,16 +1316,16 @@ public final class JobSchedulerService extends com.android.server.SystemService } switch (memLevel) { case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3; + mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT; break; case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3; + mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT; break; case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxActiveJobs = 1; + mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT; break; default: - mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; + mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT; break; } @@ -1152,10 +1333,15 @@ public final class JobSchedulerService extends com.android.server.SystemService boolean[] act = mTmpAssignAct; int[] preferredUidForContext = mTmpAssignPreferredUidForContext; int numActive = 0; + int numForeground = 0; for (int i=0; i= JobInfo.PRIORITY_TOP_APP) { + numForeground++; + } } act[i] = false; preferredUidForContext[i] = js.getPreferredUid(); @@ -1184,13 +1370,14 @@ public final class JobSchedulerService extends com.android.server.SystemService JobStatus job = contextIdToJobMap[j]; int preferredUid = preferredUidForContext[j]; if (job == null) { - if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) && + if ((numActive < mMaxActiveJobs || + (priority >= JobInfo.PRIORITY_TOP_APP && + numForeground < mConstants.FG_JOB_COUNT)) && (preferredUid == nextPending.getUid() || preferredUid == JobServiceContext.NO_PREFERRED_UID)) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. minPriorityContextId = j; - numActive++; break; } // No job on this context, but nextPending can't run here because @@ -1212,11 +1399,16 @@ public final class JobSchedulerService extends com.android.server.SystemService if (minPriorityContextId != -1) { contextIdToJobMap[minPriorityContextId] = nextPending; act[minPriorityContextId] = true; + numActive++; + if (priority >= JobInfo.PRIORITY_TOP_APP) { + numForeground++; + } } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); } + mJobPackageTracker.noteConcurrency(numActive, numForeground); for (int i=0; i 0) { - mJobs.forEachJob(new JobStatusFunctor() { - private int index = 0; - + final List jobs = mJobs.mJobSet.getAllJobs(); + Collections.sort(jobs, new Comparator() { @Override - public void process(JobStatus job) { - pw.print(" Job #"); pw.print(index++); pw.print(": "); - pw.println(job.toShortString()); - - // Skip printing details if the caller requested a filter - if (!job.shouldDump(filterUidFinal)) { - return; + public int compare(JobStatus o1, JobStatus o2) { + int uid1 = o1.getUid(); + int uid2 = o2.getUid(); + int id1 = o1.getJobId(); + int id2 = o2.getJobId(); + if (uid1 != uid2) { + return uid1 < uid2 ? -1 : 1; } - - job.dump(pw, " ", true); - pw.print(" Ready: "); - pw.print(mHandler.isReadyToBeExecutedLocked(job)); - pw.print(" (job="); - pw.print(job.isReady()); - pw.print(" pending="); - pw.print(mPendingJobs.contains(job)); - pw.print(" active="); - pw.print(isCurrentlyActiveLocked(job)); - pw.print(" user="); - pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId())); - pw.println(")"); + return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0); } }); + for (JobStatus job : jobs) { + pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": "); + pw.println(job.toShortStringExceptUniqueId()); + + // Skip printing details if the caller requested a filter + if (!job.shouldDump(filterUidFinal)) { + continue; + } + + job.dump(pw, " ", true); + pw.print(" Ready: "); + pw.print(mHandler.isReadyToBeExecutedLocked(job)); + pw.print(" (job="); + pw.print(job.isReady()); + pw.print(" pending="); + pw.print(mPendingJobs.contains(job)); + pw.print(" active="); + pw.print(isCurrentlyActiveLocked(job)); + pw.print(" user="); + pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId())); + pw.println(")"); + } } else { pw.println(" None."); } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 1f7d3128d6556..602b9c755e884 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -844,8 +844,16 @@ public class JobStore { // Inefficient; use only for testing public List getAllJobs() { ArrayList allJobs = new ArrayList(size()); - for (int i = mJobs.size(); i >= 0; i--) { - allJobs.addAll(mJobs.valueAt(i)); + for (int i = mJobs.size() - 1; i >= 0; i--) { + ArraySet jobs = mJobs.valueAt(i); + if (jobs != null) { + // Use a for loop over the ArraySet, so we don't need to make its + // optional collection class iterator implementation or have to go + // through a temporary array from toArray(). + for (int j = jobs.size() - 1; j >= 0; j--) { + allJobs.add(jobs.valueAt(j)); + } + } } return allJobs; } diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index 759303542aecb..a23af3541e196 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -18,6 +18,7 @@ package com.android.server.job.controllers; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; +import android.os.UserHandle; import android.util.Slog; import com.android.server.LocalServices; @@ -42,6 +43,7 @@ public class AppIdleController extends StateController { private static volatile AppIdleController sController; private final JobSchedulerService mJobSchedulerService; private final UsageStatsManagerInternal mUsageStatsInternal; + private boolean mInitializedParoleOn; boolean mAppIdleParoleOn; final class GlobalUpdateFunc implements JobStore.JobStatusFunctor { @@ -100,12 +102,16 @@ public class AppIdleController extends StateController { super(service, context, lock); mJobSchedulerService = service; mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); - mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn(); + mAppIdleParoleOn = true; mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener()); } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { + if (!mInitializedParoleOn) { + mInitializedParoleOn = true; + mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn(); + } String packageName = jobStatus.getSourcePackageName(); final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, jobStatus.getSourceUid(), jobStatus.getSourceUserId()); @@ -122,21 +128,24 @@ public class AppIdleController extends StateController { @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { - pw.println("AppIdle"); - pw.println("Parole On: " + mAppIdleParoleOn); + pw.print("AppIdle: parole on = "); + pw.println(mAppIdleParoleOn); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { // Skip printing details if the caller requested a filter if (!jobStatus.shouldDump(filterUid)) { return; } - pw.print(" "); + pw.print(" #"); + jobStatus.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, jobStatus.getSourceUid()); + pw.print(": "); pw.print(jobStatus.getSourcePackageName()); - pw.print(": runnable="); + pw.print(", runnable="); pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0); } }); - pw.println(); } void setAppIdleParoleOn(boolean isAppIdleParoleOn) { diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index a0cb25fb68700..f6b8ef4387b88 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -23,6 +23,7 @@ import android.content.IntentFilter; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -195,21 +196,21 @@ public class BatteryController extends StateController { @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { - pw.println("Batt."); - pw.println("Stable power: " + mChargeTracker.isOnStablePower()); - Iterator it = mTrackedTasks.iterator(); - if (it.hasNext()) { - JobStatus jobStatus = it.next(); - if (jobStatus.shouldDump(filterUid)) { - pw.print(String.valueOf(jobStatus.hashCode())); + pw.print("Battery: stable power = "); + pw.println(mChargeTracker.isOnStablePower()); + pw.print("Tracking "); + pw.print(mTrackedTasks.size()); + pw.println(":"); + for (int i = 0; i < mTrackedTasks.size(); i++) { + final JobStatus js = mTrackedTasks.get(i); + if (!js.shouldDump(filterUid)) { + continue; } + pw.print(" #"); + js.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, js.getSourceUid()); + pw.println(); } - while (it.hasNext()) { - JobStatus jobStatus = it.next(); - if (jobStatus.shouldDump(filterUid)) { - pw.print("," + String.valueOf(jobStatus.hashCode())); - } - } - pw.println(); } } diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 7d28633104cdc..2ff880c5f19ca 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -187,14 +187,20 @@ public class ConnectivityController extends StateController implements @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { - pw.println("Conn."); + pw.println("Connectivity."); + pw.print("Tracking "); + pw.print(mTrackedJobs.size()); + pw.println(":"); for (int i = 0; i < mTrackedJobs.size(); i++) { final JobStatus js = mTrackedJobs.get(i); if (js.shouldDump(filterUid)) { - pw.println(String.valueOf(js.getJobId() + "," + js.getUid()) - + ": C=" + js.hasConnectivityConstraint() - + ", UM=" + js.hasUnmeteredConstraint() - + ", NR=" + js.hasNotRoamingConstraint()); + pw.print(" #"); + js.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, js.getSourceUid()); + pw.print(": C="); pw.print(js.hasConnectivityConstraint()); + pw.print(": UM="); pw.print(js.hasUnmeteredConstraint()); + pw.print(": NR="); pw.println(js.hasNotRoamingConstraint()); } } } diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java index c1d7c5826a4fe..26660e82e8410 100644 --- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java +++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java @@ -21,6 +21,7 @@ import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +import android.os.UserHandle; import android.util.TimeUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -323,23 +324,17 @@ public class ContentObserverController extends StateController { @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { - pw.println("Content."); - boolean printed = false; + pw.println("Content:"); Iterator it = mTrackedTasks.iterator(); while (it.hasNext()) { JobStatus js = it.next(); if (!js.shouldDump(filterUid)) { continue; } - if (!printed) { - pw.print(" "); - printed = true; - } else { - pw.print(","); - } - pw.print(System.identityHashCode(js)); - } - if (printed) { + pw.print(" #"); + js.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, js.getSourceUid()); pw.println(); } int N = mObservers.size(); @@ -367,8 +362,10 @@ public class ContentObserverController extends StateController { pw.println(" Jobs:"); for (int j=0; j it = mTrackedJobs.iterator(); @@ -153,10 +158,11 @@ public class TimeController extends StateController { it.remove(); } else { // Sorted by expiry time, so take the next one and stop. nextExpiryTime = jobDeadline; + nextExpiryUid = job.getSourceUid(); break; } } - setDeadlineExpiredAlarmLocked(nextExpiryTime); + setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid); } } @@ -168,6 +174,7 @@ public class TimeController extends StateController { synchronized (mLock) { final long nowElapsedMillis = SystemClock.elapsedRealtime(); long nextDelayTime = Long.MAX_VALUE; + int nextDelayUid = 0; boolean ready = false; Iterator it = mTrackedJobs.iterator(); while (it.hasNext()) { @@ -184,25 +191,29 @@ public class TimeController extends StateController { if (job.isReady()) { ready = true; } - } else { // Keep going through list to get next delay time. + } else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) { + // If this job still doesn't have its delay constraint satisfied, + // then see if it is the next upcoming delay time for the alarm. if (nextDelayTime > jobDelayTime) { nextDelayTime = jobDelayTime; + nextDelayUid = job.getSourceUid(); } } } if (ready) { mStateChangedListener.onControllerStateChanged(); } - setDelayExpiredAlarmLocked(nextDelayTime); + setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid); } } - private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed) { + private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed, + int uid) { if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) { - setDelayExpiredAlarmLocked(delayExpiredElapsed); + setDelayExpiredAlarmLocked(delayExpiredElapsed, uid); } if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) { - setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed); + setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid); } } @@ -211,11 +222,11 @@ public class TimeController extends StateController { * delay will expire. * This alarm will wake up the phone. */ - private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis) { + private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, - mNextDelayExpiredElapsedMillis); + mNextDelayExpiredElapsedMillis, uid); } /** @@ -223,11 +234,11 @@ public class TimeController extends StateController { * deadline will expire. * This alarm will wake up the phone. */ - private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis) { + private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, - mNextJobExpiredElapsedMillis); + mNextJobExpiredElapsedMillis, uid); } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { @@ -239,7 +250,7 @@ public class TimeController extends StateController { } private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, - long alarmTimeElapsed) { + long alarmTimeElapsed, int uid) { ensureAlarmServiceLocked(); if (alarmTimeElapsed == Long.MAX_VALUE) { mAlarmService.cancel(listener); @@ -248,7 +259,7 @@ public class TimeController extends StateController { Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed); } mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed, - tag, listener, null); + AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid)); } } @@ -277,20 +288,39 @@ public class TimeController extends StateController { @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { final long nowElapsed = SystemClock.elapsedRealtime(); - pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")"); - pw.println( - "Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s"); - pw.println("Next deadline alarm in " + (mNextJobExpiredElapsedMillis - nowElapsed)/1000 - + "s"); - pw.println("Tracking:"); + pw.print("Alarms: now="); + pw.print(SystemClock.elapsedRealtime()); + pw.println(); + pw.print("Next delay alarm in "); + TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw); + pw.println(); + pw.print("Next deadline alarm in "); + TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw); + pw.println(); + pw.print("Tracking "); + pw.print(mTrackedJobs.size()); + pw.println(":"); for (JobStatus ts : mTrackedJobs) { if (!ts.shouldDump(filterUid)) { continue; } - pw.println(String.valueOf(ts.getJobId() + "," + ts.getUid()) - + ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A") - + ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A") - + ")"); + pw.print(" #"); + ts.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, ts.getSourceUid()); + pw.print(": Delay="); + if (ts.hasTimingDelayConstraint()) { + TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw); + } else { + pw.print("N/A"); + } + pw.print(", Deadline="); + if (ts.hasDeadlineConstraint()) { + TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw); + } else { + pw.print("N/A"); + } + pw.println(); } } } \ No newline at end of file