Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d273048b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow apps to specify minimum chunk size."

parents 34173d69 c14d4337
Loading
Loading
Loading
Loading
+74 −3
Original line number Diff line number Diff line
@@ -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) {
+79 −5
Original line number Diff line number Diff line
@@ -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();
    }
+1 −0
Original line number Diff line number Diff line
@@ -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();
+41 −3
Original line number Diff line number Diff line
@@ -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) {
+22 −6
Original line number Diff line number Diff line
@@ -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);
Loading