1 /*
2  * Copyright (C) 2013 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.printservice;
18 
19 import android.os.RemoteException;
20 import android.print.PrintJobId;
21 import android.print.PrintJobInfo;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 /**
26  * This class represents a print job from the perspective of a print
27  * service. It provides APIs for observing the print job state and
28  * performing operations on the print job.
29  * <p>
30  * <strong>Note: </strong> All methods of this class must be invoked on
31  * the main application thread.
32  * </p>
33  */
34 public final class PrintJob {
35 
36     private static final String LOG_TAG = "PrintJob";
37 
38     private final IPrintServiceClient mPrintServiceClient;
39 
40     private final PrintDocument mDocument;
41 
42     private PrintJobInfo mCachedInfo;
43 
PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client)44     PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) {
45         mCachedInfo = jobInfo;
46         mPrintServiceClient = client;
47         mDocument = new PrintDocument(mCachedInfo.getId(), client,
48                 jobInfo.getDocumentInfo());
49     }
50 
51     /**
52      * Gets the unique print job id.
53      *
54      * @return The id.
55      */
getId()56     public PrintJobId getId() {
57         PrintService.throwIfNotCalledOnMainThread();
58         return mCachedInfo.getId();
59     }
60 
61     /**
62      * Gets the {@link PrintJobInfo} that describes this job.
63      * <p>
64      * <strong>Node:</strong>The returned info object is a snapshot of the
65      * current print job state. Every call to this method returns a fresh
66      * info object that reflects the current print job state.
67      * </p>
68      *
69      * @return The print job info.
70      */
getInfo()71     public PrintJobInfo getInfo() {
72         PrintService.throwIfNotCalledOnMainThread();
73         if (isInImmutableState()) {
74             return mCachedInfo;
75         }
76         PrintJobInfo info = null;
77         try {
78             info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId());
79         } catch (RemoteException re) {
80             Log.e(LOG_TAG, "Couldn't get info for job: " + mCachedInfo.getId(), re);
81         }
82         if (info != null) {
83             mCachedInfo = info;
84         }
85         return mCachedInfo;
86     }
87 
88     /**
89      * Gets the printed document.
90      *
91      * @return The document.
92      */
getDocument()93     public PrintDocument getDocument() {
94         PrintService.throwIfNotCalledOnMainThread();
95         return mDocument;
96     }
97 
98     /**
99      * Gets whether this print job is queued. Such a print job is
100      * ready to be printed and can be started or cancelled.
101      *
102      * @return Whether the print job is queued.
103      *
104      * @see #start()
105      * @see #cancel()
106      */
isQueued()107     public boolean isQueued() {
108         PrintService.throwIfNotCalledOnMainThread();
109         return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
110     }
111 
112     /**
113      * Gets whether this print job is started. Such a print job is
114      * being printed and can be completed or canceled or failed.
115      *
116      * @return Whether the print job is started.
117      *
118      * @see #complete()
119      * @see #cancel()
120      * @see #fail(CharSequence)
121      */
isStarted()122     public boolean isStarted() {
123         PrintService.throwIfNotCalledOnMainThread();
124         return getInfo().getState() == PrintJobInfo.STATE_STARTED;
125     }
126 
127     /**
128      * Gets whether this print job is blocked. Such a print job is halted
129      * due to an abnormal condition and can be started or canceled or failed.
130      *
131      * @return Whether the print job is blocked.
132      *
133      * @see #start()
134      * @see #cancel()
135      * @see #fail(CharSequence)
136      */
isBlocked()137     public boolean isBlocked() {
138         PrintService.throwIfNotCalledOnMainThread();
139         return getInfo().getState() == PrintJobInfo.STATE_BLOCKED;
140     }
141 
142     /**
143      * Gets whether this print job is completed. Such a print job
144      * is successfully printed. This is a final state.
145      *
146      * @return Whether the print job is completed.
147      *
148      * @see #complete()
149      */
isCompleted()150     public boolean isCompleted() {
151         PrintService.throwIfNotCalledOnMainThread();
152         return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
153     }
154 
155     /**
156      * Gets whether this print job is failed. Such a print job is
157      * not successfully printed due to an error. This is a final state.
158      *
159      * @return Whether the print job is failed.
160      *
161      * @see #fail(CharSequence)
162      */
isFailed()163     public boolean isFailed() {
164         PrintService.throwIfNotCalledOnMainThread();
165         return getInfo().getState() == PrintJobInfo.STATE_FAILED;
166     }
167 
168     /**
169      * Gets whether this print job is cancelled. Such a print job was
170      * cancelled as a result of a user request. This is a final state.
171      *
172      * @return Whether the print job is cancelled.
173      *
174      * @see #cancel()
175      */
isCancelled()176     public boolean isCancelled() {
177         PrintService.throwIfNotCalledOnMainThread();
178         return getInfo().getState() == PrintJobInfo.STATE_CANCELED;
179     }
180 
181     /**
182      * Starts the print job. You should call this method if {@link
183      * #isQueued()} or {@link #isBlocked()} returns true and you started
184      * resumed printing.
185      *
186      * @return Whether the job was started.
187      *
188      * @see #isQueued()
189      * @see #isBlocked()
190      */
start()191     public boolean start() {
192         PrintService.throwIfNotCalledOnMainThread();
193         final int state = getInfo().getState();
194         if (state == PrintJobInfo.STATE_QUEUED
195                 || state == PrintJobInfo.STATE_BLOCKED) {
196             return setState(PrintJobInfo.STATE_STARTED, null);
197         }
198         return false;
199     }
200 
201     /**
202      * Blocks the print job. You should call this method if {@link
203      * #isStarted()} or {@link #isBlocked()} returns true and you need
204      * to block the print job. For example, the user has to add some
205      * paper to continue printing. To resume the print job call {@link
206      * #start()}.
207      *
208      * @return Whether the job was blocked.
209      *
210      * @see #isStarted()
211      * @see #isBlocked()
212      */
block(String reason)213     public boolean block(String reason) {
214         PrintService.throwIfNotCalledOnMainThread();
215         PrintJobInfo info = getInfo();
216         final int state = info.getState();
217         if (state == PrintJobInfo.STATE_STARTED
218                 || (state == PrintJobInfo.STATE_BLOCKED
219                         && !TextUtils.equals(info.getStateReason(), reason))) {
220             return setState(PrintJobInfo.STATE_BLOCKED, reason);
221         }
222         return false;
223     }
224 
225     /**
226      * Completes the print job. You should call this method if {@link
227      * #isStarted()} returns true and you are done printing.
228      *
229      * @return Whether the job as completed.
230      *
231      * @see #isStarted()
232      */
complete()233     public boolean complete() {
234         PrintService.throwIfNotCalledOnMainThread();
235         if (isStarted()) {
236             return setState(PrintJobInfo.STATE_COMPLETED, null);
237         }
238         return false;
239     }
240 
241     /**
242      * Fails the print job. You should call this method if {@link
243      * #isQueued()} or {@link #isStarted()} or {@link #isBlocked()}
244      * returns true you failed while printing.
245      *
246      * @param error The human readable, short, and translated reason
247      * for the failure.
248      * @return Whether the job was failed.
249      *
250      * @see #isQueued()
251      * @see #isStarted()
252      * @see #isBlocked()
253      */
fail(String error)254     public boolean fail(String error) {
255         PrintService.throwIfNotCalledOnMainThread();
256         if (!isInImmutableState()) {
257             return setState(PrintJobInfo.STATE_FAILED, error);
258         }
259         return false;
260     }
261 
262     /**
263      * Cancels the print job. You should call this method if {@link
264      * #isQueued()} or {@link #isStarted() or #isBlocked()} returns
265      * true and you canceled the print job as a response to a call to
266      * {@link PrintService#onRequestCancelPrintJob(PrintJob)}.
267      *
268      * @return Whether the job is canceled.
269      *
270      * @see #isStarted()
271      * @see #isQueued()
272      * @see #isBlocked()
273      */
cancel()274     public boolean cancel() {
275         PrintService.throwIfNotCalledOnMainThread();
276         if (!isInImmutableState()) {
277             return setState(PrintJobInfo.STATE_CANCELED, null);
278         }
279         return false;
280     }
281 
282     /**
283      * Sets a tag that is valid in the context of a {@link PrintService}
284      * and is not interpreted by the system. For example, a print service
285      * may set as a tag the key of the print job returned by a remote
286      * print server, if the printing is off handed to a cloud based service.
287      *
288      * @param tag The tag.
289      * @return True if the tag was set, false otherwise.
290      */
setTag(String tag)291     public boolean setTag(String tag) {
292         PrintService.throwIfNotCalledOnMainThread();
293         if (isInImmutableState()) {
294             return false;
295         }
296         try {
297             return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag);
298         } catch (RemoteException re) {
299             Log.e(LOG_TAG, "Error setting tag for job: " + mCachedInfo.getId(), re);
300         }
301         return false;
302     }
303 
304     /**
305      * Gets the print job tag.
306      *
307      * @return The tag or null.
308      *
309      * @see #setTag(String)
310      */
getTag()311     public String getTag() {
312         PrintService.throwIfNotCalledOnMainThread();
313         return getInfo().getTag();
314     }
315 
316     /**
317      * Gets the value of an advanced (printer specific) print option.
318      *
319      * @param key The option key.
320      * @return The option value.
321      */
getAdvancedStringOption(String key)322     public String getAdvancedStringOption(String key) {
323         PrintService.throwIfNotCalledOnMainThread();
324         return getInfo().getAdvancedStringOption(key);
325     }
326 
327     /**
328      * Gets whether this job has a given advanced (printer specific) print
329      * option.
330      *
331      * @param key The option key.
332      * @return Whether the option is present.
333      */
hasAdvancedOption(String key)334     public boolean hasAdvancedOption(String key) {
335         PrintService.throwIfNotCalledOnMainThread();
336         return getInfo().hasAdvancedOption(key);
337     }
338 
339     /**
340      * Gets the value of an advanced (printer specific) print option.
341      *
342      * @param key The option key.
343      * @return The option value.
344      */
getAdvancedIntOption(String key)345     public int getAdvancedIntOption(String key) {
346         PrintService.throwIfNotCalledOnMainThread();
347         return getInfo().getAdvancedIntOption(key);
348     }
349 
350     @Override
equals(Object obj)351     public boolean equals(Object obj) {
352         if (this == obj) {
353             return true;
354         }
355         if (obj == null) {
356             return false;
357         }
358         if (getClass() != obj.getClass()) {
359             return false;
360         }
361         PrintJob other = (PrintJob) obj;
362         return (mCachedInfo.getId().equals(other.mCachedInfo.getId()));
363     }
364 
365     @Override
hashCode()366     public int hashCode() {
367         return mCachedInfo.getId().hashCode();
368     }
369 
isInImmutableState()370     private boolean isInImmutableState() {
371         final int state = mCachedInfo.getState();
372         return state == PrintJobInfo.STATE_COMPLETED
373                 || state == PrintJobInfo.STATE_CANCELED
374                 || state == PrintJobInfo.STATE_FAILED;
375     }
376 
setState(int state, String error)377     private boolean setState(int state, String error) {
378         try {
379             if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
380                 // Best effort - update the state of the cached info since
381                 // we may not be able to re-fetch it later if the job gets
382                 // removed from the spooler as a result of the state change.
383                 mCachedInfo.setState(state);
384                 mCachedInfo.setStateReason(error);
385                 return true;
386             }
387         } catch (RemoteException re) {
388             Log.e(LOG_TAG, "Error setting the state of job: " + mCachedInfo.getId(), re);
389         }
390         return false;
391     }
392 }
393