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