Loading apex/jobscheduler/framework/java/android/app/job/JobInfo.java +74 −3 Original line number Diff line number Diff line Loading @@ -324,6 +324,7 @@ public class JobInfo implements Parcelable { private final NetworkRequest networkRequest; private final long networkDownloadBytes; private final long networkUploadBytes; private final long minimumNetworkChunkBytes; private final long minLatencyMillis; private final long maxExecutionDelayMillis; private final boolean isPeriodic; Loading Loading @@ -514,6 +515,17 @@ public class JobInfo implements Parcelable { return networkUploadBytes; } /** * Return the smallest piece of data that cannot be easily paused and resumed, in bytes. * * @return Smallest piece of data that cannot be easily paused and resumed, or * {@link #NETWORK_BYTES_UNKNOWN} when unknown. * @see Builder#setMinimumNetworkChunkBytes(long) */ public @BytesLong long getMinimumNetworkChunkBytes() { return minimumNetworkChunkBytes; } /** * Set for a job that does not recur periodically, to specify a delay after which the job * will be eligible for execution. This value is not set if the job recurs periodically. Loading Loading @@ -679,6 +691,9 @@ public class JobInfo implements Parcelable { if (networkUploadBytes != j.networkUploadBytes) { return false; } if (minimumNetworkChunkBytes != j.minimumNetworkChunkBytes) { return false; } if (minLatencyMillis != j.minLatencyMillis) { return false; } Loading Loading @@ -741,6 +756,7 @@ public class JobInfo implements Parcelable { } hashCode = 31 * hashCode + Long.hashCode(networkDownloadBytes); hashCode = 31 * hashCode + Long.hashCode(networkUploadBytes); hashCode = 31 * hashCode + Long.hashCode(minimumNetworkChunkBytes); hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis); hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis); hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic); Loading Loading @@ -777,6 +793,7 @@ public class JobInfo implements Parcelable { } networkDownloadBytes = in.readLong(); networkUploadBytes = in.readLong(); minimumNetworkChunkBytes = in.readLong(); minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); isPeriodic = in.readInt() == 1; Loading Loading @@ -807,6 +824,7 @@ public class JobInfo implements Parcelable { networkRequest = b.mNetworkRequest; networkDownloadBytes = b.mNetworkDownloadBytes; networkUploadBytes = b.mNetworkUploadBytes; minimumNetworkChunkBytes = b.mMinimumNetworkChunkBytes; minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; isPeriodic = b.mIsPeriodic; Loading Loading @@ -851,6 +869,7 @@ public class JobInfo implements Parcelable { } out.writeLong(networkDownloadBytes); out.writeLong(networkUploadBytes); out.writeLong(minimumNetworkChunkBytes); out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); out.writeInt(isPeriodic ? 1 : 0); Loading Loading @@ -986,6 +1005,7 @@ public class JobInfo implements Parcelable { private NetworkRequest mNetworkRequest; private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN; private ArrayList<TriggerContentUri> mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; private long mTriggerContentMaxDelay = -1; Loading Loading @@ -1038,6 +1058,7 @@ public class JobInfo implements Parcelable { mNetworkRequest = job.getRequiredNetwork(); mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes(); mTriggerContentUris = job.getTriggerContentUris() != null ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null; mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay(); Loading Loading @@ -1255,6 +1276,39 @@ public class JobInfo implements Parcelable { return this; } /** * Set the minimum size of non-resumable network traffic this job requires, in bytes. When * the upload or download can be easily paused and resumed, use this to set the smallest * size that must be transmitted between start and stop events to be considered successful. * If the transfer cannot be paused and resumed, then this should be the sum of the values * provided to {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)}. * * <p> * Apps are encouraged to provide values that are as accurate as possible since JobScheduler * will try to run the job at a time when at least the minimum chunk can be transmitted to * reduce the amount of repetitive data that's transferred. Jobs that cannot provide * reasonable estimates should use the sentinel value {@link JobInfo#NETWORK_BYTES_UNKNOWN}. * * <p> * The values provided here only reflect the minimum non-resumable traffic that will be * performed by the base job; if you're using {@link JobWorkItem} then * you also need to define the network traffic used by each work item * when constructing them. * * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and * resumed, in bytes. * @see JobInfo#getMinimumNetworkChunkBytes() * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long) */ @NonNull public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) { if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } mMinimumNetworkChunkBytes = chunkSizeBytes; return this; } /** * Specify that to run this job, the device must be charging (or be a * non-battery-powered device connected to permanent power, such as Android TV Loading Loading @@ -1647,12 +1701,29 @@ public class JobInfo implements Parcelable { /** * @hide */ public void enforceValidity() { // Check that network estimates require network type if ((networkDownloadBytes > 0 || networkUploadBytes > 0) && networkRequest == null) { public final void enforceValidity() { // Check that network estimates require network type and are reasonable values. if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0) && networkRequest == null) { throw new IllegalArgumentException( "Can't provide estimated network usage without requiring a network"); } final long estimatedTransfer; if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = networkDownloadBytes; } else { estimatedTransfer = networkUploadBytes + (networkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : networkDownloadBytes); } if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && minimumNetworkChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN && minimumNetworkChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } // Check that a deadline was not set on a periodic job. if (isPeriodic) { Loading apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java +79 −5 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.app.job; import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN; import android.annotation.BytesLong; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.os.Build; Loading @@ -33,8 +34,9 @@ import android.os.Parcelable; final public class JobWorkItem implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) final Intent mIntent; final long mNetworkDownloadBytes; final long mNetworkUploadBytes; private final long mNetworkDownloadBytes; private final long mNetworkUploadBytes; private final long mMinimumChunkBytes; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mDeliveryCount; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) Loading @@ -49,9 +51,7 @@ final public class JobWorkItem implements Parcelable { * @param intent The general Intent describing this work. */ public JobWorkItem(Intent intent) { mIntent = intent; mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; this(intent, NETWORK_BYTES_UNKNOWN, NETWORK_BYTES_UNKNOWN); } /** Loading @@ -68,9 +68,45 @@ final public class JobWorkItem implements Parcelable { * uploaded by this job work item, in bytes. */ public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) { this(intent, downloadBytes, uploadBytes, NETWORK_BYTES_UNKNOWN); } /** * Create a new piece of work, which can be submitted to * {@link JobScheduler#enqueue JobScheduler.enqueue}. * <p> * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for * details about how to estimate network traffic. * * @param intent The general Intent describing this work. * @param downloadBytes The estimated size of network traffic that will be * downloaded by this job work item, in bytes. * @param uploadBytes The estimated size of network traffic that will be * uploaded by this job work item, in bytes. * @param minimumChunkBytes The smallest piece of data that cannot be easily paused and * resumed, in bytes. */ public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) { if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && minimumChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } final long estimatedTransfer; if (uploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = downloadBytes; } else { estimatedTransfer = uploadBytes + (downloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : downloadBytes); } if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && minimumChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } mIntent = intent; mNetworkDownloadBytes = downloadBytes; mNetworkUploadBytes = uploadBytes; mMinimumChunkBytes = minimumChunkBytes; } /** Loading Loading @@ -102,6 +138,16 @@ final public class JobWorkItem implements Parcelable { return mNetworkUploadBytes; } /** * Return the smallest piece of data that cannot be easily paused and resumed, in bytes. * * @return Smallest piece of data that cannot be easily paused and resumed, or * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. */ public @BytesLong long getMinimumNetworkChunkBytes() { return mMinimumChunkBytes; } /** * Return the count of the number of times this work item has been delivered * to the job. The value will be > 1 if it has been redelivered because the job Loading Loading @@ -161,6 +207,10 @@ final public class JobWorkItem implements Parcelable { sb.append(" uploadBytes="); sb.append(mNetworkUploadBytes); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN) { sb.append(" minimumChunkBytes="); sb.append(mMinimumChunkBytes); } if (mDeliveryCount != 0) { sb.append(" dcount="); sb.append(mDeliveryCount); Loading @@ -169,6 +219,28 @@ final public class JobWorkItem implements Parcelable { return sb.toString(); } /** * @hide */ public void enforceValidity() { final long estimatedTransfer; if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = mNetworkDownloadBytes; } else { estimatedTransfer = mNetworkUploadBytes + (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : mNetworkDownloadBytes); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } } public int describeContents() { return 0; } Loading @@ -182,6 +254,7 @@ final public class JobWorkItem implements Parcelable { } out.writeLong(mNetworkDownloadBytes); out.writeLong(mNetworkUploadBytes); out.writeLong(mMinimumChunkBytes); out.writeInt(mDeliveryCount); out.writeInt(mWorkId); } Loading @@ -206,6 +279,7 @@ final public class JobWorkItem implements Parcelable { } mNetworkDownloadBytes = in.readLong(); mNetworkUploadBytes = in.readLong(); mMinimumChunkBytes = in.readLong(); mDeliveryCount = in.readInt(); mWorkId = in.readInt(); } Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -2806,6 +2806,7 @@ public class JobSchedulerService extends com.android.server.SystemService throw new NullPointerException("work is null"); } work.enforceValidity(); validateJobFlags(job, uid); final long ident = Binder.clearCallingIdentity(); Loading apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +41 −3 Original line number Diff line number Diff line Loading @@ -549,6 +549,47 @@ public final class ConnectivityController extends RestrictingController implemen */ private boolean isInsane(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { // Use the maximum possible time since it gives us an upper bound, even though the job // could end up stopping earlier. final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus); final long minimumChunkBytes = jobStatus.getMinimumNetworkChunkBytes(); if (minimumChunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidthDown = capabilities.getLinkDownstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes the minimum // chunk in time. if (bandwidthDown > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8)); if (estimatedMillis > maxJobExecutionTimeMs) { // If we'd never finish the minimum chunk before the timeout, we'd be insane! Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthDown + " kbps network would take " + estimatedMillis + "ms and job has " + maxJobExecutionTimeMs + "ms to run; that's insane!"); return true; } } final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. if (bandwidthUp > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8)); if (estimatedMillis > maxJobExecutionTimeMs) { // If we'd never finish the minimum chunk before the timeout, we'd be insane! Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp + " kbps network would take " + estimatedMillis + "ms and job has " + maxJobExecutionTimeMs + "ms to run; that's insane!"); return true; } } return false; } // Minimum chunk size isn't defined. Check using the estimated upload/download sizes. if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED) && mChargingTracker.isCharging()) { // We're charging and on an unmetered network. We don't have to be as conservative about Loading @@ -557,9 +598,6 @@ public final class ConnectivityController extends RestrictingController implemen return false; } // Use the maximum possible time since it gives us an upper bound, even though the job // could end up stopping earlier. final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus); final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes(); if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { Loading apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +22 −6 Original line number Diff line number Diff line Loading @@ -391,6 +391,7 @@ public final class JobStatus { private long mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN; private long mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN; private long mMinimumNetworkChunkBytes = JobInfo.NETWORK_BYTES_UNKNOWN; /////// Booleans that track if a job is ready to run. They should be updated whenever dependent /////// states change. Loading Loading @@ -531,7 +532,7 @@ public final class JobStatus { mInternalFlags = internalFlags; updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); if (job.getRequiredNetwork() != null) { // Later, when we check if a given network satisfies the required Loading Loading @@ -664,7 +665,7 @@ public final class JobStatus { sourcePackageName, sourceUserId, toShortString())); } pendingWork.add(work); updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); } public JobWorkItem dequeueWorkLocked() { Loading @@ -677,7 +678,7 @@ public final class JobStatus { executingWork.add(work); work.bumpDeliveryCount(); } updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); return work; } return null; Loading Loading @@ -736,7 +737,7 @@ public final class JobStatus { pendingWork = null; executingWork = null; incomingJob.nextPendingWorkId = nextPendingWorkId; incomingJob.updateEstimatedNetworkBytesLocked(); incomingJob.updateNetworkBytesLocked(); } else { // We are completely stopping the job... need to clean up work. ungrantWorkList(pendingWork); Loading @@ -744,7 +745,7 @@ public final class JobStatus { ungrantWorkList(executingWork); executingWork = null; } updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); } public void prepareLocked() { Loading Loading @@ -944,9 +945,10 @@ public final class JobStatus { } } private void updateEstimatedNetworkBytesLocked() { private void updateNetworkBytesLocked() { mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes(); if (pendingWork != null) { for (int i = 0; i < pendingWork.size(); i++) { Loading @@ -968,6 +970,12 @@ public final class JobStatus { mTotalNetworkUploadBytes += uploadBytes; } } final long chunkBytes = pendingWork.get(i).getMinimumNetworkChunkBytes(); if (mMinimumNetworkChunkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) { mMinimumNetworkChunkBytes = chunkBytes; } else if (chunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { mMinimumNetworkChunkBytes = Math.min(mMinimumNetworkChunkBytes, chunkBytes); } } } } Loading @@ -980,6 +988,10 @@ public final class JobStatus { return mTotalNetworkUploadBytes; } public long getMinimumNetworkChunkBytes() { return mMinimumNetworkChunkBytes; } /** Does this job have any sort of networking constraint? */ public boolean hasConnectivityConstraint() { // No need to check mDynamicConstraints since connectivity will only be in that list if Loading Loading @@ -1942,6 +1954,10 @@ public final class JobStatus { pw.print("Network upload bytes: "); pw.println(mTotalNetworkUploadBytes); } if (mMinimumNetworkChunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { pw.print("Minimum network chunk bytes: "); pw.println(mMinimumNetworkChunkBytes); } if (job.getMinLatencyMillis() != 0) { pw.print("Minimum latency: "); TimeUtils.formatDuration(job.getMinLatencyMillis(), pw); Loading Loading
apex/jobscheduler/framework/java/android/app/job/JobInfo.java +74 −3 Original line number Diff line number Diff line Loading @@ -324,6 +324,7 @@ public class JobInfo implements Parcelable { private final NetworkRequest networkRequest; private final long networkDownloadBytes; private final long networkUploadBytes; private final long minimumNetworkChunkBytes; private final long minLatencyMillis; private final long maxExecutionDelayMillis; private final boolean isPeriodic; Loading Loading @@ -514,6 +515,17 @@ public class JobInfo implements Parcelable { return networkUploadBytes; } /** * Return the smallest piece of data that cannot be easily paused and resumed, in bytes. * * @return Smallest piece of data that cannot be easily paused and resumed, or * {@link #NETWORK_BYTES_UNKNOWN} when unknown. * @see Builder#setMinimumNetworkChunkBytes(long) */ public @BytesLong long getMinimumNetworkChunkBytes() { return minimumNetworkChunkBytes; } /** * Set for a job that does not recur periodically, to specify a delay after which the job * will be eligible for execution. This value is not set if the job recurs periodically. Loading Loading @@ -679,6 +691,9 @@ public class JobInfo implements Parcelable { if (networkUploadBytes != j.networkUploadBytes) { return false; } if (minimumNetworkChunkBytes != j.minimumNetworkChunkBytes) { return false; } if (minLatencyMillis != j.minLatencyMillis) { return false; } Loading Loading @@ -741,6 +756,7 @@ public class JobInfo implements Parcelable { } hashCode = 31 * hashCode + Long.hashCode(networkDownloadBytes); hashCode = 31 * hashCode + Long.hashCode(networkUploadBytes); hashCode = 31 * hashCode + Long.hashCode(minimumNetworkChunkBytes); hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis); hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis); hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic); Loading Loading @@ -777,6 +793,7 @@ public class JobInfo implements Parcelable { } networkDownloadBytes = in.readLong(); networkUploadBytes = in.readLong(); minimumNetworkChunkBytes = in.readLong(); minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); isPeriodic = in.readInt() == 1; Loading Loading @@ -807,6 +824,7 @@ public class JobInfo implements Parcelable { networkRequest = b.mNetworkRequest; networkDownloadBytes = b.mNetworkDownloadBytes; networkUploadBytes = b.mNetworkUploadBytes; minimumNetworkChunkBytes = b.mMinimumNetworkChunkBytes; minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; isPeriodic = b.mIsPeriodic; Loading Loading @@ -851,6 +869,7 @@ public class JobInfo implements Parcelable { } out.writeLong(networkDownloadBytes); out.writeLong(networkUploadBytes); out.writeLong(minimumNetworkChunkBytes); out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); out.writeInt(isPeriodic ? 1 : 0); Loading Loading @@ -986,6 +1005,7 @@ public class JobInfo implements Parcelable { private NetworkRequest mNetworkRequest; private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN; private ArrayList<TriggerContentUri> mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; private long mTriggerContentMaxDelay = -1; Loading Loading @@ -1038,6 +1058,7 @@ public class JobInfo implements Parcelable { mNetworkRequest = job.getRequiredNetwork(); mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes(); mTriggerContentUris = job.getTriggerContentUris() != null ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null; mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay(); Loading Loading @@ -1255,6 +1276,39 @@ public class JobInfo implements Parcelable { return this; } /** * Set the minimum size of non-resumable network traffic this job requires, in bytes. When * the upload or download can be easily paused and resumed, use this to set the smallest * size that must be transmitted between start and stop events to be considered successful. * If the transfer cannot be paused and resumed, then this should be the sum of the values * provided to {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)}. * * <p> * Apps are encouraged to provide values that are as accurate as possible since JobScheduler * will try to run the job at a time when at least the minimum chunk can be transmitted to * reduce the amount of repetitive data that's transferred. Jobs that cannot provide * reasonable estimates should use the sentinel value {@link JobInfo#NETWORK_BYTES_UNKNOWN}. * * <p> * The values provided here only reflect the minimum non-resumable traffic that will be * performed by the base job; if you're using {@link JobWorkItem} then * you also need to define the network traffic used by each work item * when constructing them. * * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and * resumed, in bytes. * @see JobInfo#getMinimumNetworkChunkBytes() * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long) */ @NonNull public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) { if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } mMinimumNetworkChunkBytes = chunkSizeBytes; return this; } /** * Specify that to run this job, the device must be charging (or be a * non-battery-powered device connected to permanent power, such as Android TV Loading Loading @@ -1647,12 +1701,29 @@ public class JobInfo implements Parcelable { /** * @hide */ public void enforceValidity() { // Check that network estimates require network type if ((networkDownloadBytes > 0 || networkUploadBytes > 0) && networkRequest == null) { public final void enforceValidity() { // Check that network estimates require network type and are reasonable values. if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0) && networkRequest == null) { throw new IllegalArgumentException( "Can't provide estimated network usage without requiring a network"); } final long estimatedTransfer; if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = networkDownloadBytes; } else { estimatedTransfer = networkUploadBytes + (networkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : networkDownloadBytes); } if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && minimumNetworkChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } if (minimumNetworkChunkBytes != NETWORK_BYTES_UNKNOWN && minimumNetworkChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } // Check that a deadline was not set on a periodic job. if (isPeriodic) { Loading
apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java +79 −5 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.app.job; import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN; import android.annotation.BytesLong; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.os.Build; Loading @@ -33,8 +34,9 @@ import android.os.Parcelable; final public class JobWorkItem implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) final Intent mIntent; final long mNetworkDownloadBytes; final long mNetworkUploadBytes; private final long mNetworkDownloadBytes; private final long mNetworkUploadBytes; private final long mMinimumChunkBytes; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mDeliveryCount; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) Loading @@ -49,9 +51,7 @@ final public class JobWorkItem implements Parcelable { * @param intent The general Intent describing this work. */ public JobWorkItem(Intent intent) { mIntent = intent; mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; this(intent, NETWORK_BYTES_UNKNOWN, NETWORK_BYTES_UNKNOWN); } /** Loading @@ -68,9 +68,45 @@ final public class JobWorkItem implements Parcelable { * uploaded by this job work item, in bytes. */ public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) { this(intent, downloadBytes, uploadBytes, NETWORK_BYTES_UNKNOWN); } /** * Create a new piece of work, which can be submitted to * {@link JobScheduler#enqueue JobScheduler.enqueue}. * <p> * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for * details about how to estimate network traffic. * * @param intent The general Intent describing this work. * @param downloadBytes The estimated size of network traffic that will be * downloaded by this job work item, in bytes. * @param uploadBytes The estimated size of network traffic that will be * uploaded by this job work item, in bytes. * @param minimumChunkBytes The smallest piece of data that cannot be easily paused and * resumed, in bytes. */ public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) { if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && minimumChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } final long estimatedTransfer; if (uploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = downloadBytes; } else { estimatedTransfer = uploadBytes + (downloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : downloadBytes); } if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && minimumChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } mIntent = intent; mNetworkDownloadBytes = downloadBytes; mNetworkUploadBytes = uploadBytes; mMinimumChunkBytes = minimumChunkBytes; } /** Loading Loading @@ -102,6 +138,16 @@ final public class JobWorkItem implements Parcelable { return mNetworkUploadBytes; } /** * Return the smallest piece of data that cannot be easily paused and resumed, in bytes. * * @return Smallest piece of data that cannot be easily paused and resumed, or * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown. */ public @BytesLong long getMinimumNetworkChunkBytes() { return mMinimumChunkBytes; } /** * Return the count of the number of times this work item has been delivered * to the job. The value will be > 1 if it has been redelivered because the job Loading Loading @@ -161,6 +207,10 @@ final public class JobWorkItem implements Parcelable { sb.append(" uploadBytes="); sb.append(mNetworkUploadBytes); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN) { sb.append(" minimumChunkBytes="); sb.append(mMinimumChunkBytes); } if (mDeliveryCount != 0) { sb.append(" dcount="); sb.append(mDeliveryCount); Loading @@ -169,6 +219,28 @@ final public class JobWorkItem implements Parcelable { return sb.toString(); } /** * @hide */ public void enforceValidity() { final long estimatedTransfer; if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) { estimatedTransfer = mNetworkDownloadBytes; } else { estimatedTransfer = mNetworkUploadBytes + (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : mNetworkDownloadBytes); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes > estimatedTransfer) { throw new IllegalArgumentException( "Minimum chunk size can't be greater than estimated network usage"); } if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes <= 0) { throw new IllegalArgumentException("Minimum chunk size must be positive"); } } public int describeContents() { return 0; } Loading @@ -182,6 +254,7 @@ final public class JobWorkItem implements Parcelable { } out.writeLong(mNetworkDownloadBytes); out.writeLong(mNetworkUploadBytes); out.writeLong(mMinimumChunkBytes); out.writeInt(mDeliveryCount); out.writeInt(mWorkId); } Loading @@ -206,6 +279,7 @@ final public class JobWorkItem implements Parcelable { } mNetworkDownloadBytes = in.readLong(); mNetworkUploadBytes = in.readLong(); mMinimumChunkBytes = in.readLong(); mDeliveryCount = in.readInt(); mWorkId = in.readInt(); } Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -2806,6 +2806,7 @@ public class JobSchedulerService extends com.android.server.SystemService throw new NullPointerException("work is null"); } work.enforceValidity(); validateJobFlags(job, uid); final long ident = Binder.clearCallingIdentity(); Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +41 −3 Original line number Diff line number Diff line Loading @@ -549,6 +549,47 @@ public final class ConnectivityController extends RestrictingController implemen */ private boolean isInsane(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { // Use the maximum possible time since it gives us an upper bound, even though the job // could end up stopping earlier. final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus); final long minimumChunkBytes = jobStatus.getMinimumNetworkChunkBytes(); if (minimumChunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidthDown = capabilities.getLinkDownstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes the minimum // chunk in time. if (bandwidthDown > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8)); if (estimatedMillis > maxJobExecutionTimeMs) { // If we'd never finish the minimum chunk before the timeout, we'd be insane! Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthDown + " kbps network would take " + estimatedMillis + "ms and job has " + maxJobExecutionTimeMs + "ms to run; that's insane!"); return true; } } final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. if (bandwidthUp > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8)); if (estimatedMillis > maxJobExecutionTimeMs) { // If we'd never finish the minimum chunk before the timeout, we'd be insane! Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp + " kbps network would take " + estimatedMillis + "ms and job has " + maxJobExecutionTimeMs + "ms to run; that's insane!"); return true; } } return false; } // Minimum chunk size isn't defined. Check using the estimated upload/download sizes. if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED) && mChargingTracker.isCharging()) { // We're charging and on an unmetered network. We don't have to be as conservative about Loading @@ -557,9 +598,6 @@ public final class ConnectivityController extends RestrictingController implemen return false; } // Use the maximum possible time since it gives us an upper bound, even though the job // could end up stopping earlier. final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus); final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes(); if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +22 −6 Original line number Diff line number Diff line Loading @@ -391,6 +391,7 @@ public final class JobStatus { private long mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN; private long mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN; private long mMinimumNetworkChunkBytes = JobInfo.NETWORK_BYTES_UNKNOWN; /////// Booleans that track if a job is ready to run. They should be updated whenever dependent /////// states change. Loading Loading @@ -531,7 +532,7 @@ public final class JobStatus { mInternalFlags = internalFlags; updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); if (job.getRequiredNetwork() != null) { // Later, when we check if a given network satisfies the required Loading Loading @@ -664,7 +665,7 @@ public final class JobStatus { sourcePackageName, sourceUserId, toShortString())); } pendingWork.add(work); updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); } public JobWorkItem dequeueWorkLocked() { Loading @@ -677,7 +678,7 @@ public final class JobStatus { executingWork.add(work); work.bumpDeliveryCount(); } updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); return work; } return null; Loading Loading @@ -736,7 +737,7 @@ public final class JobStatus { pendingWork = null; executingWork = null; incomingJob.nextPendingWorkId = nextPendingWorkId; incomingJob.updateEstimatedNetworkBytesLocked(); incomingJob.updateNetworkBytesLocked(); } else { // We are completely stopping the job... need to clean up work. ungrantWorkList(pendingWork); Loading @@ -744,7 +745,7 @@ public final class JobStatus { ungrantWorkList(executingWork); executingWork = null; } updateEstimatedNetworkBytesLocked(); updateNetworkBytesLocked(); } public void prepareLocked() { Loading Loading @@ -944,9 +945,10 @@ public final class JobStatus { } } private void updateEstimatedNetworkBytesLocked() { private void updateNetworkBytesLocked() { mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes(); if (pendingWork != null) { for (int i = 0; i < pendingWork.size(); i++) { Loading @@ -968,6 +970,12 @@ public final class JobStatus { mTotalNetworkUploadBytes += uploadBytes; } } final long chunkBytes = pendingWork.get(i).getMinimumNetworkChunkBytes(); if (mMinimumNetworkChunkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) { mMinimumNetworkChunkBytes = chunkBytes; } else if (chunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { mMinimumNetworkChunkBytes = Math.min(mMinimumNetworkChunkBytes, chunkBytes); } } } } Loading @@ -980,6 +988,10 @@ public final class JobStatus { return mTotalNetworkUploadBytes; } public long getMinimumNetworkChunkBytes() { return mMinimumNetworkChunkBytes; } /** Does this job have any sort of networking constraint? */ public boolean hasConnectivityConstraint() { // No need to check mDynamicConstraints since connectivity will only be in that list if Loading Loading @@ -1942,6 +1954,10 @@ public final class JobStatus { pw.print("Network upload bytes: "); pw.println(mTotalNetworkUploadBytes); } if (mMinimumNetworkChunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { pw.print("Minimum network chunk bytes: "); pw.println(mMinimumNetworkChunkBytes); } if (job.getMinLatencyMillis() != 0) { pw.print("Minimum latency: "); TimeUtils.formatDuration(job.getMinLatencyMillis(), pw); Loading