1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.job;
18 
19 import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
20 
21 import android.annotation.BytesLong;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.SuppressLint;
25 import android.compat.Compatibility;
26 import android.compat.annotation.UnsupportedAppUsage;
27 import android.content.Intent;
28 import android.os.Build;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.PersistableBundle;
32 
33 /**
34  * A unit of work that can be enqueued for a job using
35  * {@link JobScheduler#enqueue JobScheduler.enqueue}.  See
36  * {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details.
37  *
38  * <p class="caution"><strong>Note:</strong> Prior to Android version
39  * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
40  * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
41  * an {@link IllegalArgumentException} if they attempted to do so. Starting with
42  * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems can be persisted alongside
43  * the hosting job. However, Intents cannot be persisted. Set a {@link PersistableBundle} using
44  * {@link Builder#setExtras(PersistableBundle)} for any information that needs to be persisted.
45  */
46 final public class JobWorkItem implements Parcelable {
47     @NonNull
48     private final PersistableBundle mExtras;
49     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
50     final Intent mIntent;
51     private final long mNetworkDownloadBytes;
52     private final long mNetworkUploadBytes;
53     private final long mMinimumChunkBytes;
54     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
55     int mDeliveryCount;
56     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
57     int mWorkId;
58     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
59     Object mGrants;
60 
61     /**
62      * Create a new piece of work, which can be submitted to
63      * {@link JobScheduler#enqueue JobScheduler.enqueue}.
64      *
65      * <p>
66      * Intents cannot be used for persisted JobWorkItems.
67      * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
68      *
69      * @param intent The general Intent describing this work.
70      */
JobWorkItem(Intent intent)71     public JobWorkItem(Intent intent) {
72         this(intent, NETWORK_BYTES_UNKNOWN, NETWORK_BYTES_UNKNOWN);
73     }
74 
75     /**
76      * Create a new piece of work, which can be submitted to
77      * {@link JobScheduler#enqueue JobScheduler.enqueue}.
78      * <p>
79      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
80      * details about how to estimate network traffic.
81      *
82      * <p>
83      * Intents cannot be used for persisted JobWorkItems.
84      * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
85      *
86      * @param intent The general Intent describing this work.
87      * @param downloadBytes The estimated size of network traffic that will be
88      *            downloaded by this job work item, in bytes.
89      * @param uploadBytes The estimated size of network traffic that will be
90      *            uploaded by this job work item, in bytes.
91      */
JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes)92     public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
93         this(intent, downloadBytes, uploadBytes, NETWORK_BYTES_UNKNOWN);
94     }
95 
96     /**
97      * Create a new piece of work, which can be submitted to
98      * {@link JobScheduler#enqueue JobScheduler.enqueue}.
99      * <p>
100      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
101      * details about how to estimate network traffic.
102      *
103      * <p>
104      * Intents cannot be used for persisted JobWorkItems.
105      * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
106      *
107      * @param intent            The general Intent describing this work.
108      * @param downloadBytes     The estimated size of network traffic that will be
109      *                          downloaded by this job work item, in bytes.
110      * @param uploadBytes       The estimated size of network traffic that will be
111      *                          uploaded by this job work item, in bytes.
112      * @param minimumChunkBytes The smallest piece of data that cannot be easily paused and
113      *                          resumed, in bytes.
114      */
JobWorkItem(@ullable Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes)115     public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes,
116             @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) {
117         mExtras = PersistableBundle.EMPTY;
118         mIntent = intent;
119         mNetworkDownloadBytes = downloadBytes;
120         mNetworkUploadBytes = uploadBytes;
121         mMinimumChunkBytes = minimumChunkBytes;
122         enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
123     }
124 
JobWorkItem(@onNull Builder builder)125     private JobWorkItem(@NonNull Builder builder) {
126         mDeliveryCount = builder.mDeliveryCount;
127         mExtras = builder.mExtras.deepCopy();
128         mIntent = builder.mIntent;
129         mNetworkDownloadBytes = builder.mNetworkDownloadBytes;
130         mNetworkUploadBytes = builder.mNetworkUploadBytes;
131         mMinimumChunkBytes = builder.mMinimumNetworkChunkBytes;
132     }
133 
134     /**
135      * Return the extras associated with this work.
136      *
137      * @see Builder#setExtras(PersistableBundle)
138      */
139     @NonNull
getExtras()140     public PersistableBundle getExtras() {
141         return mExtras;
142     }
143 
144     /**
145      * Return the Intent associated with this work.
146      */
getIntent()147     public Intent getIntent() {
148         return mIntent;
149     }
150 
151     /**
152      * Return the estimated size of download traffic that will be performed by
153      * this job, in bytes.
154      *
155      * @return Estimated size of download traffic, or
156      *         {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown.
157      */
getEstimatedNetworkDownloadBytes()158     public @BytesLong long getEstimatedNetworkDownloadBytes() {
159         return mNetworkDownloadBytes;
160     }
161 
162     /**
163      * Return the estimated size of upload traffic that will be performed by
164      * this job work item, in bytes.
165      *
166      * @return Estimated size of upload traffic, or
167      *         {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown.
168      */
getEstimatedNetworkUploadBytes()169     public @BytesLong long getEstimatedNetworkUploadBytes() {
170         return mNetworkUploadBytes;
171     }
172 
173     /**
174      * Return the smallest piece of data that cannot be easily paused and resumed, in bytes.
175      *
176      * @return Smallest piece of data that cannot be easily paused and resumed, or
177      * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown.
178      */
getMinimumNetworkChunkBytes()179     public @BytesLong long getMinimumNetworkChunkBytes() {
180         return mMinimumChunkBytes;
181     }
182 
183     /**
184      * Return the count of the number of times this work item has been delivered
185      * to the job.  The value will be > 1 if it has been redelivered because the job
186      * was stopped or crashed while it had previously been delivered but before the
187      * job had called {@link JobParameters#completeWork JobParameters.completeWork} for it.
188      */
getDeliveryCount()189     public int getDeliveryCount() {
190         return mDeliveryCount;
191     }
192 
193     /**
194      * @hide
195      */
bumpDeliveryCount()196     public void bumpDeliveryCount() {
197         mDeliveryCount++;
198     }
199 
200     /**
201      * @hide
202      */
setWorkId(int id)203     public void setWorkId(int id) {
204         mWorkId = id;
205     }
206 
207     /**
208      * @hide
209      */
getWorkId()210     public int getWorkId() {
211         return mWorkId;
212     }
213 
214     /**
215      * @hide
216      */
setGrants(Object grants)217     public void setGrants(Object grants) {
218         mGrants = grants;
219     }
220 
221     /**
222      * @hide
223      */
224     @Nullable
getGrants()225     public Object getGrants() {
226         return mGrants;
227     }
228 
toString()229     public String toString() {
230         StringBuilder sb = new StringBuilder(64);
231         sb.append("JobWorkItem{id=");
232         sb.append(mWorkId);
233         sb.append(" intent=");
234         sb.append(mIntent);
235         sb.append(" extras=");
236         sb.append(mExtras);
237         if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) {
238             sb.append(" downloadBytes=");
239             sb.append(mNetworkDownloadBytes);
240         }
241         if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN) {
242             sb.append(" uploadBytes=");
243             sb.append(mNetworkUploadBytes);
244         }
245         if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN) {
246             sb.append(" minimumChunkBytes=");
247             sb.append(mMinimumChunkBytes);
248         }
249         if (mDeliveryCount != 0) {
250             sb.append(" dcount=");
251             sb.append(mDeliveryCount);
252         }
253         sb.append("}");
254         return sb.toString();
255     }
256 
257     /**
258      * Builder class for constructing {@link JobWorkItem} objects.
259      */
260     public static final class Builder {
261         private int mDeliveryCount;
262         private PersistableBundle mExtras = PersistableBundle.EMPTY;
263         private Intent mIntent;
264         private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
265         private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
266         private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN;
267 
268         /**
269          * Initialize a new Builder to construct a {@link JobWorkItem} object.
270          */
Builder()271         public Builder() {
272         }
273 
274         /**
275          * @see JobWorkItem#getDeliveryCount()
276          * @return This object for method chaining
277          * @hide
278          */
279         @NonNull
setDeliveryCount(int deliveryCount)280         public Builder setDeliveryCount(int deliveryCount) {
281             mDeliveryCount = deliveryCount;
282             return this;
283         }
284 
285         /**
286          * Set optional extras. This can be persisted, so we only allow primitive types.
287          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
288          * @return This object for method chaining
289          * @see JobWorkItem#getExtras()
290          */
291         @NonNull
setExtras(@onNull PersistableBundle extras)292         public Builder setExtras(@NonNull PersistableBundle extras) {
293             if (extras == null) {
294                 throw new IllegalArgumentException("extras cannot be null");
295             }
296             mExtras = extras;
297             return this;
298         }
299 
300         /**
301          * Set an intent with information relevant to this work item.
302          *
303          * <p>
304          * Intents cannot be used for persisted JobWorkItems.
305          * Use {@link #setExtras(PersistableBundle)} instead for persisted JobWorkItems.
306          *
307          * @return This object for method chaining
308          * @see JobWorkItem#getIntent()
309          */
310         @NonNull
setIntent(@onNull Intent intent)311         public Builder setIntent(@NonNull Intent intent) {
312             mIntent = intent;
313             return this;
314         }
315 
316         /**
317          * Set the estimated size of network traffic that will be performed for this work item,
318          * in bytes.
319          *
320          * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
321          * details about how to estimate network traffic.
322          *
323          * @param downloadBytes The estimated size of network traffic that will be
324          *                      downloaded for this work item, in bytes.
325          * @param uploadBytes   The estimated size of network traffic that will be
326          *                      uploaded for this work item, in bytes.
327          * @return This object for method chaining
328          * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
329          * @see JobWorkItem#getEstimatedNetworkDownloadBytes()
330          * @see JobWorkItem#getEstimatedNetworkUploadBytes()
331          */
332         @NonNull
333         @SuppressLint("MissingGetterMatchingBuilder")
setEstimatedNetworkBytes(@ytesLong long downloadBytes, @BytesLong long uploadBytes)334         public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
335                 @BytesLong long uploadBytes) {
336             if (downloadBytes != NETWORK_BYTES_UNKNOWN && downloadBytes < 0) {
337                 throw new IllegalArgumentException(
338                         "Invalid network download bytes: " + downloadBytes);
339             }
340             if (uploadBytes != NETWORK_BYTES_UNKNOWN && uploadBytes < 0) {
341                 throw new IllegalArgumentException("Invalid network upload bytes: " + uploadBytes);
342             }
343             mNetworkDownloadBytes = downloadBytes;
344             mNetworkUploadBytes = uploadBytes;
345             return this;
346         }
347 
348         /**
349          * Set the minimum size of non-resumable network traffic this work item requires, in bytes.
350          * When the upload or download can be easily paused and resumed, use this to set the
351          * smallest size that must be transmitted between start and stop events to be considered
352          * successful. If the transfer cannot be paused and resumed, then this should be the sum
353          * of the values provided to {@link #setEstimatedNetworkBytes(long, long)}.
354          *
355          * See {@link JobInfo.Builder#setMinimumNetworkChunkBytes(long)} for
356          * details about how to set the minimum chunk.
357          *
358          * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and
359          *                       resumed, in bytes.
360          * @return This object for method chaining
361          * @see JobInfo.Builder#setMinimumNetworkChunkBytes(long)
362          * @see JobWorkItem#getMinimumNetworkChunkBytes()
363          * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long)
364          */
365         @NonNull
setMinimumNetworkChunkBytes(@ytesLong long chunkSizeBytes)366         public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) {
367             if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) {
368                 throw new IllegalArgumentException("Minimum chunk size must be positive");
369             }
370             mMinimumNetworkChunkBytes = chunkSizeBytes;
371             return this;
372         }
373 
374         /**
375          * @return The JobWorkItem object to hand to the JobScheduler. This object is immutable.
376          */
377         @NonNull
build()378         public JobWorkItem build() {
379             return build(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
380         }
381 
382         /** @hide */
383         @NonNull
build(boolean rejectNegativeNetworkEstimates)384         public JobWorkItem build(boolean rejectNegativeNetworkEstimates) {
385             JobWorkItem jobWorkItem = new JobWorkItem(this);
386             jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
387             return jobWorkItem;
388         }
389     }
390 
391     /**
392      * @hide
393      */
enforceValidity(boolean rejectNegativeNetworkEstimates)394     public void enforceValidity(boolean rejectNegativeNetworkEstimates) {
395         if (rejectNegativeNetworkEstimates) {
396             if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN && mNetworkUploadBytes < 0) {
397                 throw new IllegalArgumentException(
398                         "Invalid network upload bytes: " + mNetworkUploadBytes);
399             }
400             if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN && mNetworkDownloadBytes < 0) {
401                 throw new IllegalArgumentException(
402                         "Invalid network download bytes: " + mNetworkDownloadBytes);
403             }
404         }
405         final long estimatedTransfer;
406         if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) {
407             estimatedTransfer = mNetworkDownloadBytes;
408         } else {
409             estimatedTransfer = mNetworkUploadBytes
410                     + (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : mNetworkDownloadBytes);
411         }
412         if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN
413                 && estimatedTransfer != NETWORK_BYTES_UNKNOWN
414                 && mMinimumChunkBytes > estimatedTransfer) {
415             throw new IllegalArgumentException(
416                     "Minimum chunk size can't be greater than estimated network usage");
417         }
418         if (mMinimumChunkBytes != NETWORK_BYTES_UNKNOWN && mMinimumChunkBytes <= 0) {
419             throw new IllegalArgumentException("Minimum chunk size must be positive");
420         }
421     }
422 
describeContents()423     public int describeContents() {
424         return 0;
425     }
426 
writeToParcel(Parcel out, int flags)427     public void writeToParcel(Parcel out, int flags) {
428         if (mIntent != null) {
429             out.writeInt(1);
430             mIntent.writeToParcel(out, 0);
431         } else {
432             out.writeInt(0);
433         }
434         out.writePersistableBundle(mExtras);
435         out.writeLong(mNetworkDownloadBytes);
436         out.writeLong(mNetworkUploadBytes);
437         out.writeLong(mMinimumChunkBytes);
438         out.writeInt(mDeliveryCount);
439         out.writeInt(mWorkId);
440     }
441 
442     public static final @android.annotation.NonNull Parcelable.Creator<JobWorkItem> CREATOR
443             = new Parcelable.Creator<JobWorkItem>() {
444         public JobWorkItem createFromParcel(Parcel in) {
445             return new JobWorkItem(in);
446         }
447 
448         public JobWorkItem[] newArray(int size) {
449             return new JobWorkItem[size];
450         }
451     };
452 
453     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
JobWorkItem(Parcel in)454     JobWorkItem(Parcel in) {
455         if (in.readInt() != 0) {
456             mIntent = Intent.CREATOR.createFromParcel(in);
457         } else {
458             mIntent = null;
459         }
460         final PersistableBundle extras = in.readPersistableBundle();
461         mExtras = extras != null ? extras : PersistableBundle.EMPTY;
462         mNetworkDownloadBytes = in.readLong();
463         mNetworkUploadBytes = in.readLong();
464         mMinimumChunkBytes = in.readLong();
465         mDeliveryCount = in.readInt();
466         mWorkId = in.readInt();
467     }
468 }
469