1 /*
2  * Copyright (C) 2014 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ClipData;
23 import android.net.Network;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.PersistableBundle;
30 import android.os.RemoteException;
31 
32 /**
33  * Contains the parameters used to configure/identify your job. You do not create this object
34  * yourself, instead it is handed in to your application by the System.
35  */
36 public class JobParameters implements Parcelable {
37 
38     /** @hide */
39     public static final int REASON_CANCELED = JobProtoEnums.STOP_REASON_CANCELLED; // 0.
40     /** @hide */
41     public static final int REASON_CONSTRAINTS_NOT_SATISFIED =
42             JobProtoEnums.STOP_REASON_CONSTRAINTS_NOT_SATISFIED; //1.
43     /** @hide */
44     public static final int REASON_PREEMPT = JobProtoEnums.STOP_REASON_PREEMPT; // 2.
45     /** @hide */
46     public static final int REASON_TIMEOUT = JobProtoEnums.STOP_REASON_TIMEOUT; // 3.
47     /** @hide */
48     public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4.
49     /** @hide */
50     public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5.
51     /**
52      * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
53      * bucket.
54      *
55      * @hide
56      */
57     public static final int REASON_RESTRICTED_BUCKET =
58             JobProtoEnums.STOP_REASON_RESTRICTED_BUCKET; // 6.
59 
60     /**
61      * All the stop reason codes. This should be regarded as an immutable array at runtime.
62      *
63      * Note the order of these values will affect "dumpsys batterystats", and we do not want to
64      * change the order of existing fields, so adding new fields is okay but do not remove or
65      * change existing fields. When deprecating a field, just replace that with "-1" in this array.
66      *
67      * @hide
68      */
69     public static final int[] JOB_STOP_REASON_CODES = {
70             REASON_CANCELED,
71             REASON_CONSTRAINTS_NOT_SATISFIED,
72             REASON_PREEMPT,
73             REASON_TIMEOUT,
74             REASON_DEVICE_IDLE,
75             REASON_DEVICE_THERMAL,
76             REASON_RESTRICTED_BUCKET,
77     };
78 
79     /**
80      * @hide
81      */
82     // TODO(142420609): make it @SystemApi for mainline
83     @NonNull
getReasonCodeDescription(int reasonCode)84     public static String getReasonCodeDescription(int reasonCode) {
85         switch (reasonCode) {
86             case REASON_CANCELED: return "canceled";
87             case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
88             case REASON_PREEMPT: return "preempt";
89             case REASON_TIMEOUT: return "timeout";
90             case REASON_DEVICE_IDLE: return "device_idle";
91             case REASON_DEVICE_THERMAL: return "thermal";
92             case REASON_RESTRICTED_BUCKET: return "restricted_bucket";
93             default: return "unknown:" + reasonCode;
94         }
95     }
96 
97     /** @hide */
98     // @SystemApi TODO make it a system api for mainline
99     @NonNull
getJobStopReasonCodes()100     public static int[] getJobStopReasonCodes() {
101         return JOB_STOP_REASON_CODES;
102     }
103 
104     @UnsupportedAppUsage
105     private final int jobId;
106     private final PersistableBundle extras;
107     private final Bundle transientExtras;
108     private final ClipData clipData;
109     private final int clipGrantFlags;
110     @UnsupportedAppUsage
111     private final IBinder callback;
112     private final boolean overrideDeadlineExpired;
113     private final Uri[] mTriggeredContentUris;
114     private final String[] mTriggeredContentAuthorities;
115     private final Network network;
116 
117     private int stopReason; // Default value of stopReason is REASON_CANCELED
118     private String debugStopReason; // Human readable stop reason for debugging.
119 
120     /** @hide */
JobParameters(IBinder callback, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, Uri[] triggeredContentUris, String[] triggeredContentAuthorities, Network network)121     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
122             Bundle transientExtras, ClipData clipData, int clipGrantFlags,
123             boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
124             String[] triggeredContentAuthorities, Network network) {
125         this.jobId = jobId;
126         this.extras = extras;
127         this.transientExtras = transientExtras;
128         this.clipData = clipData;
129         this.clipGrantFlags = clipGrantFlags;
130         this.callback = callback;
131         this.overrideDeadlineExpired = overrideDeadlineExpired;
132         this.mTriggeredContentUris = triggeredContentUris;
133         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
134         this.network = network;
135     }
136 
137     /**
138      * @return The unique id of this job, specified at creation time.
139      */
getJobId()140     public int getJobId() {
141         return jobId;
142     }
143 
144     /**
145      * Reason onStopJob() was called on this job.
146      * @hide
147      */
getStopReason()148     public int getStopReason() {
149         return stopReason;
150     }
151 
152     /**
153      * Reason onStopJob() was called on this job.
154      * @hide
155      */
getDebugStopReason()156     public String getDebugStopReason() {
157         return debugStopReason;
158     }
159 
160     /**
161      * @return The extras you passed in when constructing this job with
162      * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
163      * never be null. If you did not set any extras this will be an empty bundle.
164      */
getExtras()165     public @NonNull PersistableBundle getExtras() {
166         return extras;
167     }
168 
169     /**
170      * @return The transient extras you passed in when constructing this job with
171      * {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will
172      * never be null. If you did not set any extras this will be an empty bundle.
173      */
getTransientExtras()174     public @NonNull Bundle getTransientExtras() {
175         return transientExtras;
176     }
177 
178     /**
179      * @return The clip you passed in when constructing this job with
180      * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null
181      * if it was not set.
182      */
getClipData()183     public @Nullable ClipData getClipData() {
184         return clipData;
185     }
186 
187     /**
188      * @return The clip grant flags you passed in when constructing this job with
189      * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0
190      * if it was not set.
191      */
getClipGrantFlags()192     public int getClipGrantFlags() {
193         return clipGrantFlags;
194     }
195 
196     /**
197      * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this
198      * provides an easy way to tell whether the job is being executed due to the deadline
199      * expiring. Note: If the job is running because its deadline expired, it implies that its
200      * constraints will not be met.
201      */
isOverrideDeadlineExpired()202     public boolean isOverrideDeadlineExpired() {
203         return overrideDeadlineExpired;
204     }
205 
206     /**
207      * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this
208      * reports which URIs have triggered the job.  This will be null if either no URIs have
209      * triggered it (it went off due to a deadline or other reason), or the number of changed
210      * URIs is too large to report.  Whether or not the number of URIs is too large, you can
211      * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was
212      * triggered due to any content changes and the authorities they are associated with.
213      */
getTriggeredContentUris()214     public @Nullable Uri[] getTriggeredContentUris() {
215         return mTriggeredContentUris;
216     }
217 
218     /**
219      * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this
220      * reports which content authorities have triggered the job.  It will only be null if no
221      * authorities have triggered it -- that is, the job executed for some other reason, such
222      * as a deadline expiring.  If this is non-null, you can use {@link #getTriggeredContentUris()}
223      * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum
224      * number it can reported).
225      */
getTriggeredContentAuthorities()226     public @Nullable String[] getTriggeredContentAuthorities() {
227         return mTriggeredContentAuthorities;
228     }
229 
230     /**
231      * Return the network that should be used to perform any network requests
232      * for this job.
233      * <p>
234      * Devices may have multiple active network connections simultaneously, or
235      * they may not have a default network route at all. To correctly handle all
236      * situations like this, your job should always use the network returned by
237      * this method instead of implicitly using the default network route.
238      * <p>
239      * Note that the system may relax the constraints you originally requested,
240      * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
241      * a metered network when there is a surplus of metered data available.
242      *
243      * @return the network that should be used to perform any network requests
244      *         for this job, or {@code null} if this job didn't set any required
245      *         network type.
246      * @see JobInfo.Builder#setRequiredNetworkType(int)
247      */
getNetwork()248     public @Nullable Network getNetwork() {
249         return network;
250     }
251 
252     /**
253      * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
254      * currently running job.  Calling this method when there is no more work available and all
255      * previously dequeued work has been completed will result in the system taking care of
256      * stopping the job for you --
257      * you should not call {@link JobService#jobFinished(JobParameters, boolean)} yourself
258      * (otherwise you risk losing an upcoming JobWorkItem that is being enqueued at the same time).
259      *
260      * <p>Once you are done with the {@link JobWorkItem} returned by this method, you must call
261      * {@link #completeWork(JobWorkItem)} with it to inform the system that you are done
262      * executing the work.  The job will not be finished until all dequeued work has been
263      * completed.  You do not, however, have to complete each returned work item before deqeueing
264      * the next one -- you can use {@link #dequeueWork()} multiple times before completing
265      * previous work if you want to process work in parallel, and you can complete the work
266      * in whatever order you want.</p>
267      *
268      * <p>If the job runs to the end of its available time period before all work has been
269      * completed, it will stop as normal.  You should return true from
270      * {@link JobService#onStopJob(JobParameters)} in order to have the job rescheduled, and by
271      * doing so any pending as well as remaining uncompleted work will be re-queued
272      * for the next time the job runs.</p>
273      *
274      * <p>This example shows how to construct a JobService that will serially dequeue and
275      * process work that is available for it:</p>
276      *
277      * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java
278      *      service}
279      *
280      * @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null.
281      * If null is returned, the system will also stop the job if all work has also been completed.
282      * (This means that for correct operation, you must always call dequeueWork() after you have
283      * completed other work, to check either for more work or allow the system to stop the job.)
284      */
dequeueWork()285     public @Nullable JobWorkItem dequeueWork() {
286         try {
287             return getCallback().dequeueWork(getJobId());
288         } catch (RemoteException e) {
289             throw e.rethrowFromSystemServer();
290         }
291     }
292 
293     /**
294      * Report the completion of executing a {@link JobWorkItem} previously returned by
295      * {@link #dequeueWork()}.  This tells the system you are done with the
296      * work associated with that item, so it will not be returned again.  Note that if this
297      * is the last work in the queue, completing it here will <em>not</em> finish the overall
298      * job -- for that to happen, you still need to call {@link #dequeueWork()}
299      * again.
300      *
301      * <p>If you are enqueueing work into a job, you must call this method for each piece
302      * of work you process.  Do <em>not</em> call
303      * {@link JobService#jobFinished(JobParameters, boolean)}
304      * or else you can lose work in your queue.</p>
305      *
306      * @param work The work you have completed processing, as previously returned by
307      * {@link #dequeueWork()}
308      */
completeWork(@onNull JobWorkItem work)309     public void completeWork(@NonNull JobWorkItem work) {
310         try {
311             if (!getCallback().completeWork(getJobId(), work.getWorkId())) {
312                 throw new IllegalArgumentException("Given work is not active: " + work);
313             }
314         } catch (RemoteException e) {
315             throw e.rethrowFromSystemServer();
316         }
317     }
318 
319     /** @hide */
320     @UnsupportedAppUsage
getCallback()321     public IJobCallback getCallback() {
322         return IJobCallback.Stub.asInterface(callback);
323     }
324 
JobParameters(Parcel in)325     private JobParameters(Parcel in) {
326         jobId = in.readInt();
327         extras = in.readPersistableBundle();
328         transientExtras = in.readBundle();
329         if (in.readInt() != 0) {
330             clipData = ClipData.CREATOR.createFromParcel(in);
331             clipGrantFlags = in.readInt();
332         } else {
333             clipData = null;
334             clipGrantFlags = 0;
335         }
336         callback = in.readStrongBinder();
337         overrideDeadlineExpired = in.readInt() == 1;
338         mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
339         mTriggeredContentAuthorities = in.createStringArray();
340         if (in.readInt() != 0) {
341             network = Network.CREATOR.createFromParcel(in);
342         } else {
343             network = null;
344         }
345         stopReason = in.readInt();
346         debugStopReason = in.readString();
347     }
348 
349     /** @hide */
setStopReason(int reason, String debugStopReason)350     public void setStopReason(int reason, String debugStopReason) {
351         stopReason = reason;
352         this.debugStopReason = debugStopReason;
353     }
354 
355     @Override
describeContents()356     public int describeContents() {
357         return 0;
358     }
359 
360     @Override
writeToParcel(Parcel dest, int flags)361     public void writeToParcel(Parcel dest, int flags) {
362         dest.writeInt(jobId);
363         dest.writePersistableBundle(extras);
364         dest.writeBundle(transientExtras);
365         if (clipData != null) {
366             dest.writeInt(1);
367             clipData.writeToParcel(dest, flags);
368             dest.writeInt(clipGrantFlags);
369         } else {
370             dest.writeInt(0);
371         }
372         dest.writeStrongBinder(callback);
373         dest.writeInt(overrideDeadlineExpired ? 1 : 0);
374         dest.writeTypedArray(mTriggeredContentUris, flags);
375         dest.writeStringArray(mTriggeredContentAuthorities);
376         if (network != null) {
377             dest.writeInt(1);
378             network.writeToParcel(dest, flags);
379         } else {
380             dest.writeInt(0);
381         }
382         dest.writeInt(stopReason);
383         dest.writeString(debugStopReason);
384     }
385 
386     public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
387         @Override
388         public JobParameters createFromParcel(Parcel in) {
389             return new JobParameters(in);
390         }
391 
392         @Override
393         public JobParameters[] newArray(int size) {
394             return new JobParameters[size];
395         }
396     };
397 }
398