From a06ec6a9435f9555142e700f54cf20278bc1982f Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 13 Feb 2017 10:08:42 -0800 Subject: [PATCH] Add new job scheduler constraints for "battery not low". Clean up the implementation of boolean constraints so that they are in a single flags value that gets propagated all of the way from JobInfo.Builder in to the JobStatus. Much simpler and easier to add new constraints! Also introduce some shell commands to make it easier to write tests against the job scheduler (and other things tied to power). One of the big things here is that there is a new sequence number that propagates with battery updates, which we can look for in the job scheduler to determine when a change the test has made to battery state has actually gotten applied, to allow it to safely and minimally wait until executing the condition being tested. Test: New BatteryConstraintTest suite added. Change-Id: I11076d90b80ec25ee604c29b6a6dc2337fd867ce --- api/current.txt | 2 + api/system-current.txt | 2 + api/test-current.txt | 2 + core/java/android/app/job/JobInfo.java | 73 +++++++++++---- core/java/android/os/BatteryManager.java | 7 ++ .../com/android/server/BatteryService.java | 68 ++++++++++---- .../android/server/job/JobPackageTracker.java | 2 +- .../server/job/JobSchedulerService.java | 51 ++++++++++- .../server/job/JobSchedulerShellCommand.java | 90 +++++++++++++++---- .../java/com/android/server/job/JobStore.java | 3 + .../job/controllers/BatteryController.java | 63 +++++++++++-- .../server/job/controllers/JobStatus.java | 53 ++++++----- .../com/android/server/job/JobStoreTest.java | 3 +- 13 files changed, 340 insertions(+), 79 deletions(-) diff --git a/api/current.txt b/api/current.txt index b1d70c0ab7a7d..1d58c28b5ceb8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6718,6 +6718,7 @@ package android.app.job { method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris(); method public boolean isPeriodic(); method public boolean isPersisted(); + method public boolean isRequireBatteryNotLow(); method public boolean isRequireCharging(); method public boolean isRequireDeviceIdle(); method public void writeToParcel(android.os.Parcel, int); @@ -6744,6 +6745,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setPeriodic(long, long); method public android.app.job.JobInfo.Builder setPersisted(boolean); method public android.app.job.JobInfo.Builder setRequiredNetworkType(int); + method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean); method public android.app.job.JobInfo.Builder setRequiresCharging(boolean); method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean); method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle); diff --git a/api/system-current.txt b/api/system-current.txt index 924c79c9da180..bdcfa6fdeae32 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7110,6 +7110,7 @@ package android.app.job { method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris(); method public boolean isPeriodic(); method public boolean isPersisted(); + method public boolean isRequireBatteryNotLow(); method public boolean isRequireCharging(); method public boolean isRequireDeviceIdle(); method public void writeToParcel(android.os.Parcel, int); @@ -7136,6 +7137,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setPeriodic(long, long); method public android.app.job.JobInfo.Builder setPersisted(boolean); method public android.app.job.JobInfo.Builder setRequiredNetworkType(int); + method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean); method public android.app.job.JobInfo.Builder setRequiresCharging(boolean); method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean); method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle); diff --git a/api/test-current.txt b/api/test-current.txt index c8bd890e83b4e..8b4189a8c20ba 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6744,6 +6744,7 @@ package android.app.job { method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris(); method public boolean isPeriodic(); method public boolean isPersisted(); + method public boolean isRequireBatteryNotLow(); method public boolean isRequireCharging(); method public boolean isRequireDeviceIdle(); method public void writeToParcel(android.os.Parcel, int); @@ -6770,6 +6771,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setPeriodic(long, long); method public android.app.job.JobInfo.Builder setPersisted(boolean); method public android.app.job.JobInfo.Builder setRequiredNetworkType(int); + method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean); method public android.app.job.JobInfo.Builder setRequiresCharging(boolean); method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean); method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle); diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 2d6b45d9c33de..3887556ebe7d1 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -174,12 +174,26 @@ public class JobInfo implements Parcelable { */ public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0; + /** + * @hide + */ + public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0; + + /** + * @hide + */ + public static final int CONSTRAINT_FLAG_BATTERY_NOT_LOW = 1 << 1; + + /** + * @hide + */ + public static final int CONSTRAINT_FLAG_DEVICE_IDLE = 1 << 2; + private final int jobId; private final PersistableBundle extras; private final Bundle transientExtras; private final ComponentName service; - private final boolean requireCharging; - private final boolean requireDeviceIdle; + private final int constraintFlags; private final TriggerContentUri[] triggerContentUris; private final long triggerContentUpdateDelay; private final long triggerContentMaxDelay; @@ -241,14 +255,28 @@ public class JobInfo implements Parcelable { * Whether this job needs the device to be plugged in. */ public boolean isRequireCharging() { - return requireCharging; + return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0; + } + + /** + * Whether this job needs the device's battery level to not be at below the critical threshold. + */ + public boolean isRequireBatteryNotLow() { + return (constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0; } /** * Whether this job needs the device to be in an Idle maintenance window. */ public boolean isRequireDeviceIdle() { - return requireDeviceIdle; + return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0; + } + + /** + * @hide + */ + public int getConstraintFlags() { + return constraintFlags; } /** @@ -376,8 +404,7 @@ public class JobInfo implements Parcelable { extras = in.readPersistableBundle(); transientExtras = in.readBundle(); service = in.readParcelable(null); - requireCharging = in.readInt() == 1; - requireDeviceIdle = in.readInt() == 1; + constraintFlags = in.readInt(); triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR); triggerContentUpdateDelay = in.readLong(); triggerContentMaxDelay = in.readLong(); @@ -401,8 +428,7 @@ public class JobInfo implements Parcelable { extras = b.mExtras.deepcopy(); transientExtras = b.mTransientExtras.deepcopy(); service = b.mJobService; - requireCharging = b.mRequiresCharging; - requireDeviceIdle = b.mRequiresDeviceIdle; + constraintFlags = b.mConstraintFlags; triggerContentUris = b.mTriggerContentUris != null ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()]) : null; @@ -434,8 +460,7 @@ public class JobInfo implements Parcelable { out.writePersistableBundle(extras); out.writeBundle(transientExtras); out.writeParcelable(service, flags); - out.writeInt(requireCharging ? 1 : 0); - out.writeInt(requireDeviceIdle ? 1 : 0); + out.writeInt(constraintFlags); out.writeTypedArray(triggerContentUris, flags); out.writeLong(triggerContentUpdateDelay); out.writeLong(triggerContentMaxDelay); @@ -563,8 +588,7 @@ public class JobInfo implements Parcelable { private int mPriority = PRIORITY_DEFAULT; private int mFlags; // Requirements. - private boolean mRequiresCharging; - private boolean mRequiresDeviceIdle; + private int mConstraintFlags; private int mNetworkType; private ArrayList mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; @@ -651,7 +675,21 @@ public class JobInfo implements Parcelable { * @param requiresCharging Whether or not the device is plugged in. */ public Builder setRequiresCharging(boolean requiresCharging) { - mRequiresCharging = requiresCharging; + mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING) + | (requiresCharging ? CONSTRAINT_FLAG_CHARGING : 0); + return this; + } + + /** + * Specify that to run this job, the device's battery level must not be low. + * This defaults to false. If true, the job will only run when the battery level + * is not low, which is generally the point where the user is given a "low battery" + * warning. + * @param batteryNotLow Whether or not the device's battery level must not be low. + */ + public Builder setRequiresBatteryNotLow(boolean batteryNotLow) { + mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_BATTERY_NOT_LOW) + | (batteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0); return this; } @@ -666,7 +704,8 @@ public class JobInfo implements Parcelable { * window. */ public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) { - mRequiresDeviceIdle = requiresDeviceIdle; + mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE) + | (requiresDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0); return this; } @@ -816,8 +855,8 @@ public class JobInfo implements Parcelable { */ public JobInfo build() { // Allow jobs with no constraints - What am I, a database? - if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging && - !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE && + if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 && + mNetworkType == NETWORK_TYPE_NONE && mTriggerContentUris == null) { throw new IllegalArgumentException("You're trying to build a job with no " + "constraints, this is not allowed."); @@ -843,7 +882,7 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("Can't call setTransientExtras() on a " + "persisted job"); } - if (mBackoffPolicySet && mRequiresDeviceIdle) { + if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { throw new IllegalArgumentException("An idle mode job will not respect any" + " back-off policy, so calling setBackoffCriteria with" + " setRequiresDeviceIdle is an error."); diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 3a441c7cb1ca8..9ffe2fe7a709b 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -118,6 +118,13 @@ public class BatteryManager { */ public static final String EXTRA_CHARGE_COUNTER = "charge_counter"; + /** + * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: + * Current int sequence number of the update. + * {@hide} + */ + public static final String EXTRA_SEQUENCE = "seq"; + // values for "status" field in the ACTION_BATTERY_CHANGED Intent public static final int BATTERY_STATUS_UNKNOWN = Constants.BATTERY_STATUS_UNKNOWN; public static final int BATTERY_STATUS_CHARGING = Constants.BATTERY_STATUS_CHARGING; diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 6248cab0ce107..81f137edaa320 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -130,6 +130,8 @@ public final class BatteryService extends SystemService { private int mLastMaxChargingVoltage; private int mLastChargeCounter; + private int mSequence = 1; + private int mInvalidCharger; private int mLastInvalidCharger; @@ -448,27 +450,29 @@ public final class BatteryService extends SystemService { } } - sendIntentLocked(); + mSequence++; // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some // applications may want to have smart behavior based on this. if (mPlugType != 0 && mLastPlugType == 0) { + final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); mHandler.post(new Runnable() { @Override public void run() { - Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED); - statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } }); } else if (mPlugType == 0 && mLastPlugType != 0) { + final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); mHandler.post(new Runnable() { @Override public void run() { - Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED); - statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } }); @@ -476,26 +480,33 @@ public final class BatteryService extends SystemService { if (shouldSendBatteryLowLocked()) { mSentLowBatteryBroadcast = true; + final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); mHandler.post(new Runnable() { @Override public void run() { - Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW); - statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } }); } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) { mSentLowBatteryBroadcast = false; + final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); mHandler.post(new Runnable() { @Override public void run() { - Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY); - statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } }); } + // We are doing this after sending the above broadcasts, so anything processing + // them will get the new sequence number at that point. (See for example how testing + // of JobScheduler's BatteryController works.) + sendIntentLocked(); + // Update the battery LED mLed.updateLightsLocked(); @@ -527,6 +538,7 @@ public final class BatteryService extends SystemService { int icon = getIconLocked(mBatteryProps.batteryLevel); + intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus); intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth); intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent); @@ -666,12 +678,28 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" set [ac|usb|wireless|status|level|invalid] "); + pw.println(" set [-f] [ac|usb|wireless|status|level|invalid] "); pw.println(" Force a battery property value, freezing battery state."); - pw.println(" unplug"); + pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); + pw.println(" unplug [-f]"); pw.println(" Force battery unplugged, freezing battery state."); - pw.println(" reset"); + pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); + pw.println(" reset [-f]"); pw.println(" Unfreeze battery state, returning to current hardware values."); + pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); + } + + static final int OPTION_FORCE_UPDATE = 1<<0; + + int parseOptions(Shell shell) { + String opt; + int opts = 0; + while ((opt = shell.getNextOption()) != null) { + if ("-f".equals(opt)) { + opts |= OPTION_FORCE_UPDATE; + } + } + return opts; } int onShellCommand(Shell shell, String cmd) { @@ -681,6 +709,7 @@ public final class BatteryService extends SystemService { PrintWriter pw = shell.getOutPrintWriter(); switch (cmd) { case "unplug": { + int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); if (!mUpdatesStopped) { @@ -692,12 +721,13 @@ public final class BatteryService extends SystemService { long ident = Binder.clearCallingIdentity(); try { mUpdatesStopped = true; - processValuesLocked(false); + processValuesFromShellLocked(pw, opts); } finally { Binder.restoreCallingIdentity(ident); } } break; case "set": { + int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); final String key = shell.getNextArg(); @@ -745,7 +775,7 @@ public final class BatteryService extends SystemService { long ident = Binder.clearCallingIdentity(); try { mUpdatesStopped = true; - processValuesLocked(false); + processValuesFromShellLocked(pw, opts); } finally { Binder.restoreCallingIdentity(ident); } @@ -756,6 +786,7 @@ public final class BatteryService extends SystemService { } } break; case "reset": { + int opts = parseOptions(shell); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); long ident = Binder.clearCallingIdentity(); @@ -763,7 +794,7 @@ public final class BatteryService extends SystemService { if (mUpdatesStopped) { mUpdatesStopped = false; mBatteryProps.set(mLastBatteryProps); - processValuesLocked(false); + processValuesFromShellLocked(pw, opts); } } finally { Binder.restoreCallingIdentity(ident); @@ -775,6 +806,13 @@ public final class BatteryService extends SystemService { return 0; } + private void processValuesFromShellLocked(PrintWriter pw, int opts) { + processValuesLocked((opts & OPTION_FORCE_UPDATE) != 0); + if ((opts & OPTION_FORCE_UPDATE) != 0) { + pw.println(mSequence); + } + } + private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mLock) { if (args == null || args.length == 0 || "-a".equals(args[0])) { diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java index 82e2eb425da01..0a6d8a4e24745 100644 --- a/services/core/java/com/android/server/job/JobPackageTracker.java +++ b/services/core/java/com/android/server/job/JobPackageTracker.java @@ -437,7 +437,7 @@ public final class JobPackageTracker { for (int i=0; i mActiveServices = new ArrayList<>(); /** List of controllers that will notify this service of updates to jobs. */ List mControllers; + /** Need direct access to this for testing. */ + BatteryController mBatteryController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. @@ -194,6 +196,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // 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_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_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"; @@ -207,6 +210,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; + private static final int DEFAULT_MIN_BATTERY_NOT_LOW_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; @@ -228,6 +232,11 @@ public final class JobSchedulerService extends com.android.server.SystemService * things early. */ int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT; + /** + * Minimum # of "battery not low" jobs that must be ready in order to force the JMS to + * schedule things early. + */ + int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_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. @@ -312,6 +321,8 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_IDLE_COUNT); MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT, DEFAULT_MIN_CHARGING_COUNT); + MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT, + DEFAULT_MIN_BATTERY_NOT_LOW_COUNT); MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT, DEFAULT_MIN_CONNECTIVITY_COUNT); MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT, @@ -356,6 +367,9 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("="); pw.print(MIN_CHARGING_COUNT); pw.println(); + pw.print(" "); pw.print(KEY_MIN_BATTERY_NOT_LOW_COUNT); pw.print("="); + pw.print(MIN_BATTERY_NOT_LOW_COUNT); pw.println(); + pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("="); pw.print(MIN_CONNECTIVITY_COUNT); pw.println(); @@ -785,7 +799,8 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(ConnectivityController.get(this)); mControllers.add(TimeController.get(this)); mControllers.add(IdleController.get(this)); - mControllers.add(BatteryController.get(this)); + mBatteryController = BatteryController.get(this); + mControllers.add(mBatteryController); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); mControllers.add(DeviceIdleJobsController.get(this)); @@ -1186,6 +1201,7 @@ public final class JobSchedulerService extends com.android.server.SystemService */ class MaybeReadyJobQueueFunctor implements JobStatusFunctor { int chargingCount; + int batteryNotLowCount; int idleCount; int backoffCount; int connectivityCount; @@ -1223,6 +1239,9 @@ public final class JobSchedulerService extends com.android.server.SystemService if (job.hasChargingConstraint()) { chargingCount++; } + if (job.hasBatteryNotLowConstraint()) { + batteryNotLowCount++; + } if (job.hasContentTriggerConstraint()) { contentCount++; } @@ -1241,6 +1260,7 @@ public final class JobSchedulerService extends com.android.server.SystemService idleCount >= mConstants.MIN_IDLE_COUNT || connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT || chargingCount >= mConstants.MIN_CHARGING_COUNT || + batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT || contentCount >= mConstants.MIN_CONTENT_COUNT || (runnableJobs != null && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) { @@ -1264,6 +1284,7 @@ public final class JobSchedulerService extends com.android.server.SystemService idleCount = 0; backoffCount = 0; connectivityCount = 0; + batteryNotLowCount = 0; contentCount = 0; runnableJobs = null; } @@ -1764,6 +1785,34 @@ public final class JobSchedulerService extends com.android.server.SystemService return 0; } + void setMonitorBattery(boolean enabled) { + synchronized (mLock) { + if (mBatteryController != null) { + mBatteryController.getTracker().setMonitorBatteryLocked(enabled); + } + } + } + + int getBatterySeq() { + synchronized (mLock) { + return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1; + } + } + + boolean getBatteryCharging() { + synchronized (mLock) { + return mBatteryController != null + ? mBatteryController.getTracker().isOnStablePower() : false; + } + } + + boolean getBatteryNotLow() { + synchronized (mLock) { + return mBatteryController != null + ? mBatteryController.getTracker().isBatteryNotLow() : false; + } + } + private String printContextIdToJobMap(JobStatus[] map, String initial) { StringBuilder s = new StringBuilder(initial + ": "); for (int i=0; i= 0; i--) { + final JobStatus ts = mTrackedTasks.get(i); boolean previous = ts.setChargingConstraintSatisfied(stablePower); if (previous != stablePower) { reportChange = true; } + previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow); + if (previous != batteryNotLow) { + reportChange = true; + } } } // Let the scheduler know that state has changed. This may or may not result in an @@ -127,6 +133,10 @@ public class BatteryController extends StateController { private boolean mCharging; /** Keep track of whether the battery is charged enough that we want to do work. */ private boolean mBatteryHealthy; + /** Sequence number of last broadcast. */ + private int mLastBatterySeq = -1; + + private BroadcastReceiver mMonitor; public ChargingTracker() { } @@ -149,10 +159,42 @@ public class BatteryController extends StateController { mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); } - boolean isOnStablePower() { + public void setMonitorBatteryLocked(boolean enabled) { + if (enabled) { + if (mMonitor == null) { + mMonitor = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + ChargingTracker.this.onReceive(context, intent); + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mMonitor, filter); + } + } else { + if (mMonitor != null) { + mContext.unregisterReceiver(mMonitor); + mMonitor = null; + } + } + } + + public boolean isOnStablePower() { return mCharging && mBatteryHealthy; } + public boolean isBatteryNotLow() { + return mBatteryHealthy; + } + + public boolean isMonitoring() { + return mMonitor != null; + } + + public int getSeq() { + return mLastBatterySeq; + } + @Override public void onReceive(Context context, Intent intent) { onReceiveInternal(intent); @@ -191,13 +233,20 @@ public class BatteryController extends StateController { mCharging = false; maybeReportNewChargingState(); } + mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, mLastBatterySeq); } } @Override public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { pw.print("Battery: stable power = "); - pw.println(mChargeTracker.isOnStablePower()); + pw.print(mChargeTracker.isOnStablePower()); + pw.print(", not low = "); + pw.println(mChargeTracker.isBatteryNotLow()); + if (mChargeTracker.isMonitoring()) { + pw.print("MONITORING: seq="); + pw.println(mChargeTracker.getSeq()); + } pw.print("Tracking "); pw.print(mTrackedTasks.size()); pw.println(":"); diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 4b39bf96478ce..9a55fed059f4e 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -46,16 +46,17 @@ public final class JobStatus { public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; - static final int CONSTRAINT_CHARGING = 1<<0; - static final int CONSTRAINT_TIMING_DELAY = 1<<1; - static final int CONSTRAINT_DEADLINE = 1<<2; - static final int CONSTRAINT_IDLE = 1<<3; - static final int CONSTRAINT_UNMETERED = 1<<4; - static final int CONSTRAINT_CONNECTIVITY = 1<<5; - static final int CONSTRAINT_APP_NOT_IDLE = 1<<6; - static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7; - static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<8; - static final int CONSTRAINT_NOT_ROAMING = 1<<9; + static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; + static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; + static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; + static final int CONSTRAINT_TIMING_DELAY = 1<<31; + static final int CONSTRAINT_DEADLINE = 1<<30; + static final int CONSTRAINT_UNMETERED = 1<<29; + static final int CONSTRAINT_CONNECTIVITY = 1<<28; + static final int CONSTRAINT_APP_NOT_IDLE = 1<<27; + static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; + static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25; + static final int CONSTRAINT_NOT_ROAMING = 1<<24; // Soft override: ignore constraints like time that don't affect API availability public static final int OVERRIDE_SOFT = 1; @@ -163,7 +164,7 @@ public final class JobStatus { this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.numFailures = numFailures; - int requiredConstraints = 0; + int requiredConstraints = job.getConstraintFlags(); if (job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY) { requiredConstraints |= CONSTRAINT_CONNECTIVITY; } @@ -173,18 +174,12 @@ public final class JobStatus { if (job.getNetworkType() == JobInfo.NETWORK_TYPE_NOT_ROAMING) { requiredConstraints |= CONSTRAINT_NOT_ROAMING; } - if (job.isRequireCharging()) { - requiredConstraints |= CONSTRAINT_CHARGING; - } if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { requiredConstraints |= CONSTRAINT_TIMING_DELAY; } if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { requiredConstraints |= CONSTRAINT_DEADLINE; } - if (job.isRequireDeviceIdle()) { - requiredConstraints |= CONSTRAINT_IDLE; - } if (job.getTriggerContentUris() != null) { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; } @@ -331,6 +326,14 @@ public final class JobStatus { return (requiredConstraints&CONSTRAINT_CHARGING) != 0; } + public boolean hasBatteryNotLowConstraint() { + return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0; + } + + public boolean hasPowerConstraint() { + return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0; + } + public boolean hasTimingDelayConstraint() { return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0; } @@ -379,6 +382,10 @@ public final class JobStatus { return setConstraintSatisfied(CONSTRAINT_CHARGING, state); } + boolean setBatteryNotLowConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state); + } + boolean setTimingDelayConstraintSatisfied(boolean state) { return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state); } @@ -453,13 +460,14 @@ public final class JobStatus { } static final int CONSTRAINTS_OF_INTEREST = - CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | + CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; // Soft override covers all non-"functional" constraints static final int SOFT_OVERRIDE_CONSTRAINTS = - CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE; + CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW + | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE; /** * @return Whether the constraints set on this job are satisfied. @@ -495,6 +503,7 @@ public final class JobStatus { + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME) + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")" + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging() + + ",BL=" + job.isRequireBatteryNotLow() + ",I=" + job.isRequireDeviceIdle() + ",U=" + (job.getTriggerContentUris() != null) + ",F=" + numFailures + ",P=" + job.isPersisted() @@ -550,6 +559,9 @@ public final class JobStatus { if ((constraints&CONSTRAINT_CHARGING) != 0) { pw.print(" CHARGING"); } + if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) { + pw.print(" BATTERY_NOT_LOW"); + } if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) { pw.print(" TIMING_DELAY"); } @@ -607,7 +619,8 @@ public final class JobStatus { pw.println(Integer.toHexString(job.getFlags())); } pw.print(prefix); pw.print(" Requires: charging="); - pw.print(job.isRequireCharging()); pw.print(" deviceIdle="); + pw.print(job.isRequireCharging()); pw.print(" batteryNotLow="); + pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle="); pw.println(job.isRequireDeviceIdle()); if (job.getTriggerContentUris() != null) { pw.print(prefix); pw.println(" Trigger content URIs:"); diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index f615bf32911e5..33e1a1652b225 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -10,7 +10,6 @@ import android.os.SystemClock; import android.test.AndroidTestCase; import android.test.RenamingDelegatingContext; import android.util.Log; -import android.util.ArraySet; import com.android.server.job.JobStore.JobSet; import com.android.server.job.controllers.JobStatus; @@ -278,6 +277,8 @@ public class JobStoreTest extends AndroidTestCase { assertEquals("Invalid charging constraint.", first.isRequireCharging(), second.isRequireCharging()); + assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(), + second.isRequireBatteryNotLow()); assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(), second.isRequireDeviceIdle()); assertEquals("Invalid unmetered constraint.",