From d26d4898fcc9b78f4b66118895c375384098205e Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Wed, 28 Aug 2013 14:37:54 -0700 Subject: Print spooler security and some new print service facing APIs. 1. Updated the security mode of the print spooler. Now the spooler is not signed with the system key, it is not a privileged app so if it gets compromised (PDF rendering is a potential attack vector) it cannot access dangerous permissions. Also only the system can bind to the spooler. 2. Added APIs for asking a print service to start and stop tracking a given printer. This is need for the case when the user selects the printer and the print service should do a best effort to keep the system updated for the current state of the printer. 3. Added APIs for putting a print job in a blocked state. A print service would report the print job as blocked if for some reason the printer cannot proceed, e.g. 99 pages are printed but there is no paper for the last one. The user has to add more paper and the print service can resume the job. 4. Changed the read/write APIs to use ParcelFileDescriptor instead of FileDescriptor since the latter does not have a clean API for detaching the wrapped Linux file descriptor when one wants to push it to native. 5. Added API for getting the size of the printed document so the print service can avoid handling big filed over cellular network or ask the user if needed. 6. Now the print services that are preinstalled on the system image are automatically enabled. Change-Id: Ia06c311d3d21cabb9e1368f13928e11cd0030918 --- core/java/android/print/IPrintManager.aidl | 4 +- core/java/android/print/PrintDocumentAdapter.java | 18 ++-- core/java/android/print/PrintDocumentInfo.java | 30 +++++++ .../android/print/PrintFileDocumentAdapter.java | 10 +-- core/java/android/print/PrintJobInfo.java | 62 ++++++++----- core/java/android/print/PrintManager.java | 13 ++- .../android/print/PrinterDiscoverySession.java | 35 +++++++- .../java/android/print/pdf/PrintedPdfDocument.java | 5 +- core/java/android/printservice/IPrintService.aidl | 4 +- core/java/android/printservice/PrintDocument.java | 11 ++- core/java/android/printservice/PrintJob.java | 71 ++++++++++++--- core/java/android/printservice/PrintService.java | 44 +++++++-- .../printservice/PrinterDiscoverySession.java | 100 ++++++++++++++++----- 13 files changed, 312 insertions(+), 95 deletions(-) (limited to 'core/java/android') diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index 3bfd9a1cc32b..fb6bb2e9ec90 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -41,7 +41,9 @@ interface IPrintManager { void startPrinterDiscovery(in IPrinterDiscoveryObserver observer, in List priorityList, int userId); void stopPrinterDiscovery(in IPrinterDiscoveryObserver observer, int userId); - void requestPrinterUpdate(in PrinterId printerId, int userId); + void validatePrinters(in List printerIds, int userId); + void startPrinterStateTracking(in PrinterId printerId, int userId); + void stopPrinterStateTracking(in PrinterId printerId, int userId); void destroyPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId); } diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java index 8a64e85cd07d..33b4aad58ade 100644 --- a/core/java/android/print/PrintDocumentAdapter.java +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -18,8 +18,8 @@ package android.print; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; -import java.io.FileDescriptor; import java.util.List; /** @@ -41,7 +41,7 @@ import java.util.List; *
  • * After every call to {@link #onLayout(PrintAttributes, PrintAttributes, * CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to - * {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)} + * {@link #onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal, WriteResultCallback)} * asking you to write a PDF file with the content for specific pages. *
  • *
  • @@ -64,7 +64,7 @@ import java.util.List; * PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on * the UI thread (assuming onStart initializes resources needed for layout). * This will ensure that the UI does not change while you are laying out the - * printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor, + * printed content. Then you can handle {@link #onWrite(PageRange[], ParcelFileDescriptor, * CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another * thread. This will ensure that the UI is frozen for the minimal amount of * time. Also this assumes that you will generate the printed content in @@ -150,10 +150,10 @@ public abstract class PrintDocumentAdapter { * from of a PDF file to the given file descriptor. This method is invoked * on the main thread. *

    - * After you are done writing, you should not close the - * file descriptor, rather you must invoke: {@link WriteResultCallback - * #onWriteFinished(List)}, if writing completed successfully; or {@link - * WriteResultCallback#onWriteFailed(CharSequence)}, if an error occurred. + * After you are done writing, you should close the file descriptor and + * invoke {@link WriteResultCallback #onWriteFinished(List)}, if writing + * completed successfully; or {@link WriteResultCallback#onWriteFailed( + * CharSequence)}, if an error occurred. *

    *

    * Note: If the printed content is large, it is a good @@ -171,7 +171,7 @@ public abstract class PrintDocumentAdapter { * @see WriteResultCallback * @see CancellationSignal */ - public abstract void onWrite(PageRange[] pages, FileDescriptor destination, + public abstract void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback); /** @@ -185,7 +185,7 @@ public abstract class PrintDocumentAdapter { /** * Base class for implementing a callback for the result of {@link - * PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal, + * PrintDocumentAdapter#onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal, * WriteResultCallback)}. */ public static abstract class WriteResultCallback { diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index b32961b641cf..f2b91ae93c7e 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -60,6 +60,7 @@ public final class PrintDocumentInfo implements Parcelable { private int mColorMode; private Margins mMargins; private MediaSize mMediaSize; + private long mDataSize; /** * Creates a new instance. @@ -82,6 +83,7 @@ public final class PrintDocumentInfo implements Parcelable { mColorMode = prototype.mColorMode; mMargins = prototype.mMargins; mMediaSize = prototype.mMediaSize; + mDataSize = prototype.mDataSize; } /** @@ -98,6 +100,7 @@ public final class PrintDocumentInfo implements Parcelable { mColorMode = parcel.readInt(); mMargins = Margins.createFromParcel(parcel); mMediaSize = MediaSize.createFromParcel(parcel); + mDataSize = parcel.readLong(); } /** @@ -188,6 +191,26 @@ public final class PrintDocumentInfo implements Parcelable { return mMediaSize; } + /** + * Gets the document data size in bytes. + * + * @return The data size. + */ + public long getDataSize() { + return mDataSize; + } + + /** + * Sets the document data size in bytes. + * + * @param dataSize The data size. + * + * @hide + */ + public void setDataSize(long dataSize) { + mDataSize = dataSize; + } + @Override public int describeContents() { return 0; @@ -203,6 +226,7 @@ public final class PrintDocumentInfo implements Parcelable { parcel.writeInt(mColorMode); mMargins.writeToParcel(parcel); mMediaSize.writeToParcel(parcel); + parcel.writeLong(mDataSize); } @Override @@ -217,6 +241,8 @@ public final class PrintDocumentInfo implements Parcelable { result = prime * result + mColorMode; result = prime * result + (mMargins != null ? mMargins.hashCode() : 0); result = prime * result + (mMediaSize != null ? mMediaSize.hashCode() : 0); + result = prime * result + (int) mDataSize; + result = prime * result + (int) mDataSize >> 32; return result; } @@ -264,6 +290,9 @@ public final class PrintDocumentInfo implements Parcelable { } else if (!mMediaSize.equals(other.mMediaSize)) { return false; } + if (mDataSize != other.mDataSize) { + return false; + } return true; } @@ -279,6 +308,7 @@ public final class PrintDocumentInfo implements Parcelable { builder.append(", colorMode=").append(PrintAttributes.colorModeToString(mColorMode)); builder.append(", margins=").append(mMargins); builder.append(", mediaSize=").append(mMediaSize); + builder.append(", size=").append(mDataSize); builder.append("}"); return builder.toString(); } diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java index dbc8b6f9c6fb..b9053961baf7 100644 --- a/core/java/android/print/PrintFileDocumentAdapter.java +++ b/core/java/android/print/PrintFileDocumentAdapter.java @@ -21,6 +21,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; +import android.os.ParcelFileDescriptor; import android.util.Log; import com.android.internal.R; @@ -28,7 +29,6 @@ import com.android.internal.R; import libcore.io.IoUtils; import java.io.File; -import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -81,7 +81,7 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter { } @Override - public void onWrite(PageRange[] pages, FileDescriptor destination, + public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback); mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, @@ -90,13 +90,13 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter { private final class WriteFileAsyncTask extends AsyncTask { - private final FileDescriptor mDestination; + private final ParcelFileDescriptor mDestination; private final WriteResultCallback mResultCallback; private final CancellationSignal mCancellationSignal; - public WriteFileAsyncTask(FileDescriptor destination, + public WriteFileAsyncTask(ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { mDestination = destination; mResultCallback = callback; @@ -112,7 +112,7 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter { @Override protected Void doInBackground(Void... params) { InputStream in = null; - OutputStream out = new FileOutputStream(mDestination); + OutputStream out = new FileOutputStream(mDestination.getFileDescriptor()); final byte[] buffer = new byte[8192]; try { in = new FileInputStream(mFile); diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java index 602f3c1f2a44..b919ad602f16 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -43,6 +43,13 @@ public final class PrintJobInfo implements Parcelable { */ public static final int STATE_ANY_VISIBLE_TO_CLIENTS = -2; + /** + * Constant for matching any active print job state. + * + * @hide + */ + public static final int STATE_ANY_ACTIVE = -3; + /** * Print job state: The print job is being created but not yet * ready to be printed. @@ -55,7 +62,7 @@ public final class PrintJobInfo implements Parcelable { public static final int STATE_CREATED = 1; /** - * Print job status: The print jobs is created, it is ready + * Print job state: The print jobs is created, it is ready * to be printed and should be processed. *

    * Next valid states: {@link #STATE_STARTED}, {@link #STATE_FAILED}, @@ -65,40 +72,49 @@ public final class PrintJobInfo implements Parcelable { public static final int STATE_QUEUED = 2; /** - * Print job status: The print job is being printed. + * Print job state: The print job is being printed. *

    * Next valid states: {@link #STATE_COMPLETED}, {@link #STATE_FAILED}, - * {@link #STATE_CANCELED} + * {@link #STATE_CANCELED}, {@link #STATE_BLOCKED} *

    */ public static final int STATE_STARTED = 3; /** - * Print job status: The print job was successfully printed. + * Print job state: The print job is blocked. + *

    + * Next valid states: {@link #STATE_FAILED}, {@link #STATE_CANCELED}, + * {@link #STATE_STARTED} + *

    + */ + public static final int STATE_BLOCKED = 4; + + /** + * Print job state: The print job was successfully printed. * This is a terminal state. *

    * Next valid states: None *

    */ - public static final int STATE_COMPLETED = 4; + public static final int STATE_COMPLETED = 5; /** - * Print job status: The print job was printing but printing failed. + * Print job state: The print job was printing but printing failed. * This is a terminal state. *

    * Next valid states: None *

    */ - public static final int STATE_FAILED = 5; + public static final int STATE_FAILED = 6; /** - * Print job status: The print job was canceled. + * Print job state: The print job was canceled. * This is a terminal state. *

    * Next valid states: None *

    */ - public static final int STATE_CANCELED = 6; + public static final int STATE_CANCELED = 7; /** The unique print job id. */ private int mId; @@ -127,8 +143,8 @@ public final class PrintJobInfo implements Parcelable { /** How many copies to print. */ private int mCopies; - /** Failure reason if this job failed. */ - private String mFailureReason; + /** Reason for the print job being in its current state. */ + private String mStateReason; /** The pages to print */ private PageRange[] mPageRanges; @@ -155,7 +171,7 @@ public final class PrintJobInfo implements Parcelable { mUserId = other.mUserId; mTag = other.mTag; mCopies = other.mCopies; - mFailureReason = other.mFailureReason; + mStateReason = other.mStateReason; mPageRanges = other.mPageRanges; mAttributes = other.mAttributes; mDocumentInfo = other.mDocumentInfo; @@ -171,7 +187,7 @@ public final class PrintJobInfo implements Parcelable { mUserId = parcel.readInt(); mTag = parcel.readString(); mCopies = parcel.readInt(); - mFailureReason = parcel.readString(); + mStateReason = parcel.readString(); if (parcel.readInt() == 1) { Parcelable[] parcelables = parcel.readParcelableArray(null); mPageRanges = new PageRange[parcelables.length]; @@ -377,25 +393,27 @@ public final class PrintJobInfo implements Parcelable { } /** - * The failure reason if this print job failed. + * Gets the reason for the print job being in the current state. * - * @return The failure reason. + * @return The reason, or null if there is no reason or the + * reason is unknown. * * @hide */ - public String getFailureReason() { - return mFailureReason; + public String getStateReason() { + return mStateReason; } /** - * The failure reason if this print job failed. + * Sets the reason for the print job being in the current state. * - * @param failureReason The failure reason. + * @param stateReason The reason, or null if there is no reason + * or the reason is unknown. * * @hide */ - public void setFailureReason(String failureReason) { - mFailureReason = failureReason; + public void setStateReason(String stateReason) { + mStateReason = stateReason; } /** @@ -476,7 +494,7 @@ public final class PrintJobInfo implements Parcelable { parcel.writeInt(mUserId); parcel.writeString(mTag); parcel.writeInt(mCopies); - parcel.writeString(mFailureReason); + parcel.writeString(mStateReason); if (mPageRanges != null) { parcel.writeInt(1); parcel.writeParcelableArray(mPageRanges, flags); diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index d3e35c3f6445..6e32c05fc24c 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -36,7 +36,6 @@ import com.android.internal.os.SomeArgs; import libcore.io.IoUtils; import java.io.File; -import java.io.FileDescriptor; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; @@ -163,7 +162,7 @@ public final class PrintManager { * @param pdfFile The PDF file to print. * @param documentInfo Information about the printed document. * @param attributes The default print job attributes. - * @return The created print job. + * @return The created print job on success or null on failure. * * @see PrintJob */ @@ -181,7 +180,7 @@ public final class PrintManager { * @param printJobName A name for the new print job. * @param documentAdapter An adapter that emits the document to print. * @param attributes The default print job attributes. - * @return The created print job. + * @return The created print job on success or null on failure. * * @see PrintJob */ @@ -279,7 +278,7 @@ public final class PrintManager { } SomeArgs args = SomeArgs.obtain(); args.arg1 = pages; - args.arg2 = fd.getFileDescriptor(); + args.arg2 = fd; args.arg3 = callback; args.argi1 = sequence; mHandler.removeMessages(MyHandler.MSG_WRITE); @@ -342,7 +341,7 @@ public final class PrintManager { case MSG_WRITE: { SomeArgs args = (SomeArgs) message.obj; PageRange[] pages = (PageRange[]) args.arg1; - FileDescriptor fd = (FileDescriptor) args.arg2; + ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg2; IWriteResultCallback callback = (IWriteResultCallback) args.arg3; final int sequence = args.argi1; args.recycle(); @@ -428,12 +427,12 @@ public final class PrintManager { } private final class MyWriteResultCallback extends WriteResultCallback { - private FileDescriptor mFd; + private ParcelFileDescriptor mFd; private int mSequence; private IWriteResultCallback mCallback; public MyWriteResultCallback(IWriteResultCallback callback, - FileDescriptor fd, int sequence) { + ParcelFileDescriptor fd, int sequence) { mFd = fd; mSequence = sequence; mCallback = callback; diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java index 8fbdd9c00577..46f0bef8fe20 100644 --- a/core/java/android/print/PrinterDiscoverySession.java +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -74,6 +74,7 @@ public final class PrinterDiscoverySession { public final void startPrinterDisovery(List priorityList) { if (isDestroyed()) { Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + return; } if (!mIsPrinterDiscoveryStarted) { mIsPrinterDiscoveryStarted = true; @@ -88,6 +89,7 @@ public final class PrinterDiscoverySession { public final void stopPrinterDiscovery() { if (isDestroyed()) { Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed"); + return; } if (mIsPrinterDiscoveryStarted) { mIsPrinterDiscoveryStarted = false; @@ -99,14 +101,39 @@ public final class PrinterDiscoverySession { } } - public final void requestPrinterUpdate(PrinterId printerId) { + public final void startPrinterStateTracking(PrinterId printerId) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring start printer state tracking - session destroyed"); + return; + } + try { + mPrintManager.startPrinterStateTracking(printerId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error starting printer state tracking", re); + } + } + + public final void stopPrinterStateTracking(PrinterId printerId) { if (isDestroyed()) { - Log.w(LOG_TAG, "Ignoring reqeust printer update - session destroyed"); + Log.w(LOG_TAG, "Ignoring stop printer state tracking - session destroyed"); + return; + } + try { + mPrintManager.stopPrinterStateTracking(printerId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error stoping printer state tracking", re); + } + } + + public final void validatePrinters(List printerIds) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring validate printers - session destroyed"); + return; } try { - mPrintManager.requestPrinterUpdate(printerId, mUserId); + mPrintManager.validatePrinters(printerIds, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error requesting printer update", re); + Log.e(LOG_TAG, "Error validating printers", re); } } diff --git a/core/java/android/print/pdf/PrintedPdfDocument.java b/core/java/android/print/pdf/PrintedPdfDocument.java index a3be38bb1f71..bee17efd485a 100644 --- a/core/java/android/print/pdf/PrintedPdfDocument.java +++ b/core/java/android/print/pdf/PrintedPdfDocument.java @@ -111,7 +111,10 @@ public final class PrintedPdfDocument { * @see #finishPage(Page) */ public Page startPage(int pageNumber) { - PageInfo pageInfo = new PageInfo.Builder(mPageSize, 0).create(); + PageInfo pageInfo = new PageInfo + .Builder(mPageSize, 0) + .setContentSize(mContentSize) + .create(); Page page = mDocument.startPage(pageInfo); return page; } diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl index 2cee1d84d0fb..ee3661937395 100644 --- a/core/java/android/printservice/IPrintService.aidl +++ b/core/java/android/printservice/IPrintService.aidl @@ -33,6 +33,8 @@ oneway interface IPrintService { void createPrinterDiscoverySession(); void startPrinterDiscovery(in List priorityList); void stopPrinterDiscovery(); - void requestPrinterUpdate(in PrinterId printerId); + void validatePrinters(in List printerIds); + void startPrinterStateTracking(in PrinterId printerId); + void stopPrinterStateTracking(in PrinterId printerId); void destroyPrinterDiscoverySession(); } diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java index 7437dc57d34b..8292cfbccc6f 100644 --- a/core/java/android/printservice/PrintDocument.java +++ b/core/java/android/printservice/PrintDocument.java @@ -21,12 +21,15 @@ import android.os.RemoteException; import android.print.PrintDocumentInfo; import android.util.Log; -import java.io.FileDescriptor; import java.io.IOException; /** * This class represents a printed document from the perspective of a print * service. It exposes APIs to query the document and obtain its data. + *

    + * Note: All methods of this class must be executed on the + * main application thread. + *

    */ public final class PrintDocument { @@ -51,6 +54,7 @@ public final class PrintDocument { * @return The document info. */ public PrintDocumentInfo getInfo() { + PrintService.throwIfNotCalledOnMainThread(); return mInfo; } @@ -64,7 +68,8 @@ public final class PrintDocument { * * @return A file descriptor for reading the data. */ - public FileDescriptor getData() { + public ParcelFileDescriptor getData() { + PrintService.throwIfNotCalledOnMainThread(); ParcelFileDescriptor source = null; ParcelFileDescriptor sink = null; try { @@ -72,7 +77,7 @@ public final class PrintDocument { source = fds[0]; sink = fds[1]; mPrintServiceClient.writePrintJobData(sink, mPrintJobId); - return source.getFileDescriptor(); + return source; } catch (IOException ioe) { Log.e(LOG_TAG, "Error calling getting print job data!", ioe); } catch (RemoteException re) { diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java index d2fbef2b578e..8bae9d677572 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -18,6 +18,7 @@ package android.printservice; import android.os.RemoteException; import android.print.PrintJobInfo; +import android.text.TextUtils; import android.util.Log; /** @@ -122,6 +123,21 @@ public final class PrintJob { return getInfo().getState() == PrintJobInfo.STATE_STARTED; } + /** + * Gets whether this print job is blocked. Such a print job is halted + * due to an abnormal condition and can be started or canceled or failed. + * + * @return Whether the print job is blocked. + * + * @see #start() + * @see #cancel() + * @see #fail(CharSequence) + */ + public boolean isBlocked() { + PrintService.throwIfNotCalledOnMainThread(); + return getInfo().getState() == PrintJobInfo.STATE_BLOCKED; + } + /** * Gets whether this print job is completed. Such a print job * is successfully printed. This is a final state. @@ -163,20 +179,48 @@ public final class PrintJob { /** * Starts the print job. You should call this method if {@link - * #isQueued()} returns true and you started printing. + * #isQueued()} or {@link #isBlocked()} returns true and you started + * resumed printing. * - * @return Whether the job as started. + * @return Whether the job was started. * * @see #isQueued() + * @see #isBlocked() */ public boolean start() { PrintService.throwIfNotCalledOnMainThread(); - if (isQueued()) { + final int state = getInfo().getState(); + if (state == PrintJobInfo.STATE_QUEUED + || state == PrintJobInfo.STATE_BLOCKED) { return setState(PrintJobInfo.STATE_STARTED, null); } return false; } + /** + * Blocks the print job. You should call this method if {@link + * #isStarted()} or {@link #isBlocked()} returns true and you need + * to block the print job. For example, the user has to add some + * paper to continue printing. To resume the print job call {@link + * #start()}. + * + * @return Whether the job was blocked. + * + * @see #isStarted() + * @see #isBlocked() + */ + public boolean block(String reason) { + PrintService.throwIfNotCalledOnMainThread(); + PrintJobInfo info = getInfo(); + final int state = info.getState(); + if (state == PrintJobInfo.STATE_STARTED + || (state == PrintJobInfo.STATE_BLOCKED + && !TextUtils.equals(info.getStateReason(), reason))) { + return setState(PrintJobInfo.STATE_BLOCKED, reason); + } + return false; + } + /** * Completes the print job. You should call this method if {@link * #isStarted()} returns true and you are done printing. @@ -195,8 +239,8 @@ public final class PrintJob { /** * Fails the print job. You should call this method if {@link - * #isQueued()} or {@link #isStarted()} returns true you failed - * while printing. + * #isQueued()} or {@link #isStarted()} or {@link #isBlocked()} + * returns true you failed while printing. * * @param error The human readable, short, and translated reason * for the failure. @@ -204,10 +248,11 @@ public final class PrintJob { * * @see #isQueued() * @see #isStarted() + * @see #isBlocked() */ public boolean fail(String error) { PrintService.throwIfNotCalledOnMainThread(); - if (isQueued() || isStarted()) { + if (!isInImmutableState()) { return setState(PrintJobInfo.STATE_FAILED, error); } return false; @@ -215,18 +260,19 @@ public final class PrintJob { /** * Cancels the print job. You should call this method if {@link - * #isQueued()} or {@link #isStarted()} returns true and you canceled - * the print job as a response to a call to {@link - * PrintService#onRequestCancelPrintJob(PrintJob)}. + * #isQueued()} or {@link #isStarted() or #isBlocked()} returns + * true and you canceled the print job as a response to a call to + * {@link PrintService#onRequestCancelPrintJob(PrintJob)}. * * @return Whether the job is canceled. * * @see #isStarted() * @see #isQueued() + * @see #isBlocked() */ public boolean cancel() { PrintService.throwIfNotCalledOnMainThread(); - if (isQueued() || isStarted()) { + if (!isInImmutableState()) { return setState(PrintJobInfo.STATE_CANCELED, null); } return false; @@ -277,7 +323,8 @@ public final class PrintJob { private boolean isInImmutableState() { final int state = mCachedInfo.getState(); return state == PrintJobInfo.STATE_COMPLETED - || state == PrintJobInfo.STATE_CANCELED; + || state == PrintJobInfo.STATE_CANCELED + || state == PrintJobInfo.STATE_FAILED; } private boolean setState(int state, String error) { @@ -287,7 +334,7 @@ public final class PrintJob { // we may not be able to re-fetch it later if the job gets // removed from the spooler as a result of the state change. mCachedInfo.setState(state); - mCachedInfo.setFailureReason(error); + mCachedInfo.setStateReason(error); return true; } } catch (RemoteException re) { diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index f6c0a9adee72..96552af92e64 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -314,8 +314,20 @@ public abstract class PrintService extends Service { } @Override - public void requestPrinterUpdate(PrinterId printerId) { - mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE, + public void validatePrinters(List printerIds) { + mHandler.obtainMessage(ServiceHandler.MSG_VALIDATE_PRINTERS, + printerIds).sendToTarget(); + } + + @Override + public void startPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_STATE_TRACKING, + printerId).sendToTarget(); + } + + @Override + public void stopPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(ServiceHandler.MSG_STOP_PRINTER_STATE_TRACKING, printerId).sendToTarget(); } @@ -344,10 +356,12 @@ public abstract class PrintService extends Service { public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2; public static final int MSG_START_PRINTER_DISCOVERY = 3; public static final int MSG_STOP_PRINTER_DISCOVERY = 4; - public static final int MSG_REQUEST_PRINTER_UPDATE = 5; - public static final int MSG_ON_PRINTJOB_QUEUED = 6; - public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 7; - public static final int MSG_SET_CLEINT = 8; + public static final int MSG_VALIDATE_PRINTERS = 5; + public static final int MSG_START_PRINTER_STATE_TRACKING = 6; + public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7; + public static final int MSG_ON_PRINTJOB_QUEUED = 8; + public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 9; + public static final int MSG_SET_CLEINT = 10; public ServiceHandler(Looper looper) { super(looper, null, true); @@ -391,10 +405,24 @@ public abstract class PrintService extends Service { } } break; - case MSG_REQUEST_PRINTER_UPDATE: { + case MSG_VALIDATE_PRINTERS: { + if (mDiscoverySession != null) { + List printerIds = (List) message.obj; + mDiscoverySession.validatePrinters(printerIds); + } + } break; + + case MSG_START_PRINTER_STATE_TRACKING: { + if (mDiscoverySession != null) { + PrinterId printerId = (PrinterId) message.obj; + mDiscoverySession.startPrinterStateTracking(printerId); + } + } break; + + case MSG_STOP_PRINTER_STATE_TRACKING: { if (mDiscoverySession != null) { PrinterId printerId = (PrinterId) message.obj; - mDiscoverySession.requestPrinterUpdate(printerId); + mDiscoverySession.stopPrinterStateTracking(printerId); } } break; diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java index 8b959a68690a..1f86ecc8bbd1 100644 --- a/core/java/android/printservice/PrinterDiscoverySession.java +++ b/core/java/android/printservice/PrinterDiscoverySession.java @@ -53,15 +53,23 @@ import java.util.List; * session. Printers are not persisted across sessions. *

    *

    - * The system will make a call to - * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you - * need to update a given printer. It is possible that you add a printer without + * The system will make a call to {@link #onValidatePrinters(List)} if you + * need to update some printers. It is possible that you add a printer without * specifying its capabilities. This enables you to avoid querying all discovered * printers for their capabilities, rather querying the capabilities of a printer * only if necessary. For example, the system will request that you update a printer - * if it gets selected by the user. If you did not report the printer capabilities - * when adding it, you must do so after the system requests a printer update. - * Otherwise, the printer will be ignored. + * if it gets selected by the user. When validating printers you do not need to + * provide the printers' capabilities but may do so. + *

    + *

    + * If the system is interested in being constantly updated for the state of a + * printer you will receive a call to {@link #onStartPrinterStateTracking(PrinterId)} + * after which you will have to do a best effort to keep the system updated for + * changes in the printer state and capabilities. You also must + * update the printer capabilities if you did not provide them when adding it, or + * the printer will be ignored. When the system is no longer interested in getting + * updates for a printer you will receive a call to {@link #onStopPrinterStateTracking( + * PrinterId)}. *

    *

    * Note: All callbacks in this class are executed on the main @@ -115,7 +123,7 @@ public abstract class PrinterDiscoverySession { * the printer that was added but not removed. *

    * Note: Calls to this method after the session is - * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. *

    * * @return The printers. @@ -139,7 +147,7 @@ public abstract class PrinterDiscoverySession { * times during the life of this session. Duplicates will be ignored. *

    * Note: Calls to this method after the session is - * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. *

    * * @param printers The printers to add. @@ -218,7 +226,7 @@ public abstract class PrinterDiscoverySession { * call this method multiple times during the lifetime of this session. *

    * Note: Calls to this method after the session is - * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. *

    * * @param printerIds The ids of the removed printers. @@ -293,7 +301,7 @@ public abstract class PrinterDiscoverySession { * during the lifetime of this session. *

    * Note: Calls to this method after the session is - * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored. + * destroyed, that is after the {@link #onDestroy()} callback, will be ignored. *

    * * @param printers The printers to update. @@ -441,7 +449,9 @@ public abstract class PrinterDiscoverySession { *

    * Note: You are also given a list of printers whose availability * has to be checked first. For example, these printers could be the user's favorite - * ones, therefore they have to be verified first. + * ones, therefore they have to be verified first. You do not need + * to provide the capabilities of the printers, rather verify whether they exist + * similarly to {@link #onValidatePrinters(List)}. *

    * * @param priorityList The list of printers to validate first. Never null. @@ -463,9 +473,28 @@ public abstract class PrinterDiscoverySession { public abstract void onStopPrinterDiscovery(); /** - * Requests that you update a printer. You are responsible for updating - * the printer by also reporting its capabilities via calling {@link - * #updatePrinters(List)}. + * Callback asking you to validate that the given printers are valid, that + * is they exist. You are responsible for checking whether these printers + * exist and for the ones that do exist notify the system via calling + * {@link #updatePrinters(List)}. + *

    + * Note: You are not required to provide + * the printer capabilities when updating the printers that do exist. + *

    + * + * @param printerIds The printers to validate. + * + * @see #updatePrinters(List) + * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) + * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) + */ + public abstract void onValidatePrinters(List printerIds); + + /** + * Callback asking you to start tracking the state of a printer. Tracking + * the state means that you should do a best effort to observe the state + * of this printer and notify the system if that state changes via calling + * {@link #updatePrinters(List)}. *

    * Note: A printer can be initially added without its * capabilities to avoid polling printers that the user will not select. @@ -473,18 +502,33 @@ public abstract class PrinterDiscoverySession { * printer including its capabilities. Otherwise, the * printer will be ignored. *

    - * A scenario when you may be requested to update a printer is if the user - * selects it and the system has to present print options UI based on the - * printer's capabilities. + *

    + * A scenario when you may be requested to track a printer's state is if + * the user selects that printer and the system has to present print + * options UI based on the printer's capabilities. In this case the user + * should be promptly informed if, for example, the printer becomes + * unavailable. *

    * - * @param printerId The printer id. + * @param printerId The printer to start tracking. * + * @see #onStopPrinterStateTracking(PrinterId) * @see #updatePrinters(List) * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) */ - public abstract void onRequestPrinterUpdate(PrinterId printerId); + public abstract void onStartPrinterStateTracking(PrinterId printerId); + + /** + * Callback asking you to stop tracking the state of a printer. The passed + * in printer id is the one for which you received a call to {@link + * #onStartPrinterStateTracking(PrinterId)}. + * + * @param printerId The printer to stop tracking. + * + * @see #onStartPrinterStateTracking(PrinterId) + */ + public abstract void onStopPrinterStateTracking(PrinterId printerId); /** * Notifies you that the session is destroyed. After this callback is invoked @@ -538,9 +582,21 @@ public abstract class PrinterDiscoverySession { } } - void requestPrinterUpdate(PrinterId printerId) { - if (!mIsDestroyed) { - onRequestPrinterUpdate(printerId); + void validatePrinters(List printerIds) { + if (!mIsDestroyed && mObserver != null) { + onValidatePrinters(printerIds); + } + } + + void startPrinterStateTracking(PrinterId printerId) { + if (!mIsDestroyed && mObserver != null) { + onStartPrinterStateTracking(printerId); + } + } + + void stopPrinterStateTracking(PrinterId printerId) { + if (!mIsDestroyed && mObserver != null) { + onStopPrinterStateTracking(printerId); } } -- cgit v1.2.3