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.",