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
17package android.printservice;
18
19import android.os.RemoteException;
20import android.print.PrintJobId;
21import android.print.PrintJobInfo;
22import android.text.TextUtils;
23import 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 */
34public 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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
345    public int getAdvancedIntOption(String key) {
346        PrintService.throwIfNotCalledOnMainThread();
347        return getInfo().getAdvancedIntOption(key);
348    }
349
350    @Override
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
366    public int hashCode() {
367        return mCachedInfo.getId().hashCode();
368    }
369
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
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