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