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 PrintJob(@onNull Context context, @NonNull PrintJobInfo jobInfo, @NonNull IPrintServiceClient client)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 getId()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 getInfo()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 getDocument()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 isQueued()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 isStarted()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 isBlocked()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 isCompleted()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 isFailed()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 isCancelled()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 start()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 block(@ullable String reason)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 complete()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 fail(@ullable String error)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 cancel()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 setProgress(@loatRangefrom=0.0, to=1.0) float progress)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 setStatus(@ullable CharSequence status)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 setStatus(@tringRes int statusResId)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 setTag(@onNull String tag)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 getTag()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 getAdvancedStringOption(String key)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 hasAdvancedOption(String key)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 getAdvancedIntOption(String key)433 public int getAdvancedIntOption(String key) { 434 PrintService.throwIfNotCalledOnMainThread(); 435 return getInfo().getAdvancedIntOption(key); 436 } 437 438 @Override equals(Object obj)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 hashCode()454 public int hashCode() { 455 return mCachedInfo.getId().hashCode(); 456 } 457 isInImmutableState()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 setState(int state, @Nullable String error)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