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
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index 2cee1d8..ee36619 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -33,6 +33,8 @@
     void createPrinterDiscoverySession();
     void startPrinterDiscovery(in List<PrinterId> priorityList);
     void stopPrinterDiscovery();
-    void requestPrinterUpdate(in PrinterId printerId);
+    void validatePrinters(in List<PrinterId> 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 7437dc5..8292cfbc 100644
--- a/core/java/android/printservice/PrintDocument.java
+++ b/core/java/android/printservice/PrintDocument.java
@@ -21,12 +21,15 @@
 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.
+ * <p>
+ * <strong>Note: </strong> All methods of this class must be executed on the
+ * main application thread.
+ * </p>
  */
 public final class PrintDocument {
 
@@ -51,6 +54,7 @@
      * @return The document info.
      */
     public PrintDocumentInfo getInfo() {
+        PrintService.throwIfNotCalledOnMainThread();
         return mInfo;
     }
 
@@ -64,7 +68,8 @@
      *
      * @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 @@
             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 d2fbef2..8bae9d6 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -18,6 +18,7 @@
 
 import android.os.RemoteException;
 import android.print.PrintJobInfo;
+import android.text.TextUtils;
 import android.util.Log;
 
 /**
@@ -123,6 +124,21 @@
     }
 
     /**
+     * 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,21 +179,49 @@
 
     /**
      * 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 @@
 
     /**
      * 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 @@
      *
      * @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 @@
 
     /**
      * 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 @@
     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 @@
                 // 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 f6c0a9a..96552af 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -314,8 +314,20 @@
             }
 
             @Override
-            public void requestPrinterUpdate(PrinterId printerId) {
-                mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE,
+            public void validatePrinters(List<PrinterId> 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 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 @@
                     }
                 } break;
 
-                case MSG_REQUEST_PRINTER_UPDATE: {
+                case MSG_VALIDATE_PRINTERS: {
+                    if (mDiscoverySession != null) {
+                        List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+                        mDiscoverySession.validatePrinters(printerIds);
+                    }
+                } break;
+
+                case MSG_START_PRINTER_STATE_TRACKING: {
                     if (mDiscoverySession != null) {
                         PrinterId printerId = (PrinterId) message.obj;
-                        mDiscoverySession.requestPrinterUpdate(printerId);
+                        mDiscoverySession.startPrinterStateTracking(printerId);
+                    }
+                } break;
+
+                case MSG_STOP_PRINTER_STATE_TRACKING: {
+                    if (mDiscoverySession != null) {
+                        PrinterId printerId = (PrinterId) message.obj;
+                        mDiscoverySession.stopPrinterStateTracking(printerId);
                     }
                 } break;
 
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 8b959a6..1f86ecc 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -53,15 +53,23 @@
  * session. Printers are <strong>not</strong> persisted across sessions.
  * </p>
  * <p>
- * 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.
+ * </p>
+ * <p>
+ * 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 <strong>must</strong>
+ * 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)}.
  * </p>
  * <p>
  * <strong>Note: </strong> All callbacks in this class are executed on the main
@@ -115,7 +123,7 @@
      * the printer that was added but not removed.
      * <p>
      * <strong>Note: </strong> 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.
      * </p>
      *
      * @return The printers.
@@ -139,7 +147,7 @@
      * times during the life of this session. Duplicates will be ignored.
      * <p>
      * <strong>Note: </strong> 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.
      * </p>
      *
      * @param printers The printers to add.
@@ -218,7 +226,7 @@
      * call this method multiple times during the lifetime of this session.
      * <p>
      * <strong>Note: </strong> 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.
      * </p>
      *
      * @param printerIds The ids of the removed printers.
@@ -293,7 +301,7 @@
      * during the lifetime of this session.
      * <p>
      * <strong>Note: </strong> 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.
      * </p>
      *
      * @param printers The printers to update.
@@ -441,7 +449,9 @@
      * <p>
      * <strong>Note: </strong>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 <strong>not need</strong>
+     * to provide the capabilities of the printers, rather verify whether they exist
+     * similarly to {@link #onValidatePrinters(List)}.
      * </p>
      *
      * @param priorityList The list of printers to validate first. Never null.
@@ -463,9 +473,28 @@
     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)}.
+     * <p>
+     * <strong>Note: </strong> You are <strong>not required</strong> to provide
+     * the printer capabilities when updating the printers that do exist.
+     * <p>
+     *
+     * @param printerIds The printers to validate.
+     *
+     * @see #updatePrinters(List)
+     * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
+     *      PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
+     */
+    public abstract void onValidatePrinters(List<PrinterId> 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)}.
      * <p>
      * <strong>Note: </strong> A printer can be initially added without its
      * capabilities to avoid polling printers that the user will not select.
@@ -473,18 +502,33 @@
      * printer <strong>including</strong> its capabilities. Otherwise, the
      * printer will be ignored.
      * <p>
-     * 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.
+     * <p>
+     * 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.
      * </p>
      *
-     * @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 @@
         }
     }
 
-    void requestPrinterUpdate(PrinterId printerId) {
-        if (!mIsDestroyed) {
-            onRequestPrinterUpdate(printerId);
+    void validatePrinters(List<PrinterId> 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);
         }
     }