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