Allow apps to specify minimum chunk size.
JobScheduler wouldn't start a connectivity job if the app gave an estimated download/upload size and JS calculated that the job wouldn't complete successfully within the max execution time. The logic assumed that apps don't support interruptible work and that downloads/uploads are all-or-nothing. This negatively impacted apps that support interruptible/resume downloads & uploads. Adding an API to let an app indicate it supports resumable network activity so that JS can be smarter about whether to start a job or not. Bug: 188429037 Test: atest CtsJobSchedulerTestCases:JobInfoTest Test: atest FrameworksMockingServicesTests:ConnectivityControllerTest Change-Id: I76bfe8d1ad3ffedd7ce66e7e31098db089749c83
This commit is contained in:
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -664,7 +665,7 @@ public final class JobStatus {
|
||||
sourcePackageName, sourceUserId, toShortString()));
|
||||
}
|
||||
pendingWork.add(work);
|
||||
updateEstimatedNetworkBytesLocked();
|
||||
updateNetworkBytesLocked();
|
||||
}
|
||||
|
||||
public JobWorkItem dequeueWorkLocked() {
|
||||
@@ -677,7 +678,7 @@ public final class JobStatus {
|
||||
executingWork.add(work);
|
||||
work.bumpDeliveryCount();
|
||||
}
|
||||
updateEstimatedNetworkBytesLocked();
|
||||
updateNetworkBytesLocked();
|
||||
return work;
|
||||
}
|
||||
return null;
|
||||
@@ -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);
|
||||
@@ -744,7 +745,7 @@ public final class JobStatus {
|
||||
ungrantWorkList(executingWork);
|
||||
executingWork = null;
|
||||
}
|
||||
updateEstimatedNetworkBytesLocked();
|
||||
updateNetworkBytesLocked();
|
||||
}
|
||||
|
||||
public void prepareLocked() {
|
||||
@@ -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++) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -7937,6 +7937,7 @@ package android.app.job {
|
||||
method public static final long getMinFlexMillis();
|
||||
method public long getMinLatencyMillis();
|
||||
method public static final long getMinPeriodMillis();
|
||||
method public long getMinimumNetworkChunkBytes();
|
||||
method @Deprecated public int getNetworkType();
|
||||
method @Nullable public android.net.NetworkRequest getRequiredNetwork();
|
||||
method @NonNull public android.content.ComponentName getService();
|
||||
@@ -7979,6 +7980,7 @@ package android.app.job {
|
||||
method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
|
||||
method @Deprecated public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
|
||||
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
|
||||
method @NonNull public android.app.job.JobInfo.Builder setMinimumNetworkChunkBytes(long);
|
||||
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
|
||||
method public android.app.job.JobInfo.Builder setPeriodic(long);
|
||||
method public android.app.job.JobInfo.Builder setPeriodic(long, long);
|
||||
@@ -8071,11 +8073,13 @@ package android.app.job {
|
||||
public final class JobWorkItem implements android.os.Parcelable {
|
||||
ctor public JobWorkItem(android.content.Intent);
|
||||
ctor public JobWorkItem(android.content.Intent, long, long);
|
||||
ctor public JobWorkItem(@Nullable android.content.Intent, long, long, long);
|
||||
method public int describeContents();
|
||||
method public int getDeliveryCount();
|
||||
method public long getEstimatedNetworkDownloadBytes();
|
||||
method public long getEstimatedNetworkUploadBytes();
|
||||
method public android.content.Intent getIntent();
|
||||
method public long getMinimumNetworkChunkBytes();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,11 @@ public class ConnectivityControllerTest {
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
|
||||
DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
final JobInfo.Builder jobWithMinChunk = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
|
||||
DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setMinimumNetworkChunkBytes(DataUnit.KIBIBYTES.toBytes(100))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
|
||||
final ArgumentCaptor<BroadcastReceiver> chargingCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
@@ -170,18 +175,51 @@ public class ConnectivityControllerTest {
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
|
||||
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
|
||||
assertFalse(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
|
||||
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
|
||||
// Slow downstream
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
|
||||
assertFalse(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1).build(), mConstants));
|
||||
// Slow upstream
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
assertFalse(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
// Medium network is fine for min chunk
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(5)
|
||||
.setLinkDownstreamBandwidthKbps(5).build(), mConstants));
|
||||
assertTrue(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(5)
|
||||
.setLinkDownstreamBandwidthKbps(5).build(), mConstants));
|
||||
// Medium downstream
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(5).build(), mConstants));
|
||||
assertTrue(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(5).build(), mConstants));
|
||||
// Medium upstream
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(5)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
assertTrue(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(5)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
// Fast network looks great
|
||||
assertTrue(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
assertTrue(controller.isSatisfied(createJobStatus(jobWithMinChunk), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1024).build(), mConstants));
|
||||
// Slow network still good given time
|
||||
assertTrue(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
|
||||
@@ -241,7 +279,6 @@ public class ConnectivityControllerTest {
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
|
||||
|
||||
|
||||
// Suspended networks aren't usable.
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilitiesBuilder().removeCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
|
||||
Reference in New Issue
Block a user