Refinement of the print service APIs.

1. Factored out the printer discovery APIs of a print service in a
   dedicated session object that is created by the print service on
   demand. This ensures that added/removed/updated printers from
   one session do not interfere with another session.

2. Updated the app facing APIs to pass in a document info along
   with a printed file. Also exposed the print file adapter so
   apps that create a temporary file for printing can intercept
   when it is read by the system so the file can be deleted.

3. Updated the print service documentation.

Change-Id: I3473d586c26d8bda1cf7e2bdacb441aa9df982ed
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 46857e4..8db2169 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -17,7 +17,7 @@
 package android.print;
 
 import android.content.ComponentName;
-import android.print.IPrinterDiscoveryObserver;
+import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrinterId;
 import android.print.PrintJobInfo;
 
@@ -28,10 +28,8 @@
  * @hide
  */
 oneway interface IPrintSpoolerClient {
+    void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
     void onPrintJobQueued(in PrintJobInfo printJob);
-    void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
-    void onStopPrinterDiscovery();
-    void onRequestUpdatePrinters(in List<PrinterId> printerIds);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
 }
diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoverySessionController.aidl
similarity index 70%
rename from core/java/android/print/IPrinterDiscoveryObserver.aidl
rename to core/java/android/print/IPrinterDiscoverySessionController.aidl
index deabbcb..13116ef 100644
--- a/core/java/android/print/IPrinterDiscoveryObserver.aidl
+++ b/core/java/android/print/IPrinterDiscoverySessionController.aidl
@@ -17,15 +17,14 @@
 package android.print;
 
 import android.print.PrinterId;
-import android.print.PrinterInfo;
 
 /**
- * Interface for observing printer discovery.
+* Interface for the controlling part of a printer discovery session.
  *
  * @hide
  */
-oneway interface IPrinterDiscoveryObserver {
-    void onPrintersAdded(in List<PrinterInfo> printers);
-    void onPrintersRemoved(in List<PrinterId> printers);
-    void onPrintersUpdated(in List<PrinterInfo> printers);
+oneway interface IPrinterDiscoverySessionController {
+    void open(in List<PrinterId> priorityList);
+    void requestPrinterUpdate(in PrinterId printerId);
+    void close();
 }
diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
similarity index 68%
copy from core/java/android/print/IPrinterDiscoveryObserver.aidl
copy to core/java/android/print/IPrinterDiscoverySessionObserver.aidl
index deabbcb..a78924c 100644
--- a/core/java/android/print/IPrinterDiscoveryObserver.aidl
+++ b/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
@@ -16,16 +16,18 @@
 
 package android.print;
 
+import android.print.IPrinterDiscoverySessionController;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 
 /**
- * Interface for observing printer discovery.
+ * Interface for the observing part of a printer discovery session.
  *
  * @hide
  */
-oneway interface IPrinterDiscoveryObserver {
+oneway interface IPrinterDiscoverySessionObserver {
+    void setController(IPrinterDiscoverySessionController controller);
     void onPrintersAdded(in List<PrinterInfo> printers);
-    void onPrintersRemoved(in List<PrinterId> printers);
-    void onPrintersUpdated(in List<PrinterInfo> printers);
+    void onPrintersRemoved(in List<PrinterId> printerIds);
+    void onPrintersUpdated(in List<PrinterInfo> printerIds);
 }
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 911e380..a902c72 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -931,7 +931,7 @@
         }
 
         private final String mId;
-        private final CharSequence mLabel;
+        private final String mLabel;
         private final int mWidthMils;
         private final int mHeightMils;
 
@@ -948,7 +948,7 @@
          * @throws IllegalArgumentException If the widthMils is less than or equal to zero.
          * @throws IllegalArgumentException If the heightMils is less than or equal to zero.
          */
-        public MediaSize(String id, CharSequence label, int widthMils, int heightMils) {
+        public MediaSize(String id, String label, int widthMils, int heightMils) {
             if (TextUtils.isEmpty(id)) {
                 throw new IllegalArgumentException("id cannot be empty.");
             }
@@ -983,7 +983,7 @@
          *
          * @return The human readable label.
          */
-        public CharSequence getLabel() {
+        public String getLabel() {
             return mLabel;
         }
 
@@ -1007,7 +1007,7 @@
 
         void writeToParcel(Parcel parcel) {
             parcel.writeString(mId);
-            parcel.writeCharSequence(mLabel);
+            parcel.writeString(mLabel);
             parcel.writeInt(mWidthMils);
             parcel.writeInt(mHeightMils);
         }
@@ -1015,7 +1015,7 @@
         static MediaSize createFromParcel(Parcel parcel) {
             return new MediaSize(
                     parcel.readString(),
-                    parcel.readCharSequence(),
+                    parcel.readString(),
                     parcel.readInt(),
                     parcel.readInt());
         }
@@ -1076,7 +1076,7 @@
      */
     public static final class Resolution {
         private final String mId;
-        private final CharSequence mLabel;
+        private final String mLabel;
         private final int mHorizontalDpi;
         private final int mVerticalDpi;
 
@@ -1093,7 +1093,7 @@
          * @throws IllegalArgumentException If the horizontalDpi is less than or equal to zero.
          * @throws IllegalArgumentException If the verticalDpi is less than or equal to zero.
          */
-        public Resolution(String id, CharSequence label, int horizontalDpi, int verticalDpi) {
+        public Resolution(String id, String label, int horizontalDpi, int verticalDpi) {
             if (TextUtils.isEmpty(id)) {
                 throw new IllegalArgumentException("id cannot be empty.");
             }
@@ -1128,7 +1128,7 @@
          *
          * @return The human readable label.
          */
-        public CharSequence getLabel() {
+        public String getLabel() {
             return mLabel;
         }
 
@@ -1152,7 +1152,7 @@
 
         void writeToParcel(Parcel parcel) {
             parcel.writeString(mId);
-            parcel.writeCharSequence(mLabel);
+            parcel.writeString(mLabel);
             parcel.writeInt(mHorizontalDpi);
             parcel.writeInt(mVerticalDpi);
         }
@@ -1160,7 +1160,7 @@
         static Resolution createFromParcel(Parcel parcel) {
             return new Resolution(
                     parcel.readString(),
-                    parcel.readCharSequence(),
+                    parcel.readString(),
                     parcel.readInt(),
                     parcel.readInt());
         }
@@ -1364,7 +1364,7 @@
      */
     public static final class Tray {
         private final String mId;
-        private final CharSequence mLabel;
+        private final String mLabel;
 
         /**
          * Creates a new instance.
@@ -1375,7 +1375,7 @@
          * @throws IllegalArgumentException If the id is empty.
          * @throws IllegalArgumentException If the label is empty.
          */
-        public Tray(String id, CharSequence label) {
+        public Tray(String id, String label) {
             if (TextUtils.isEmpty(id)) {
                 throw new IllegalArgumentException("id cannot be empty.");
             }
@@ -1400,19 +1400,19 @@
          *
          * @return The human readable label.
          */
-        public CharSequence getLabel() {
+        public String getLabel() {
             return mLabel;
         }
 
         void writeToParcel(Parcel parcel) {
             parcel.writeString(mId);
-            parcel.writeCharSequence(mLabel);
+            parcel.writeString(mLabel);
         }
 
         static Tray createFromParcel(Parcel parcel) {
             return new Tray(
                     parcel.readString(),
-                    parcel.readCharSequence());
+                    parcel.readString());
         }
 
         @Override
@@ -1457,7 +1457,7 @@
         }
     }
 
-    private static String duplexModeToString(int duplexMode) {
+    static String duplexModeToString(int duplexMode) {
         switch (duplexMode) {
             case DUPLEX_MODE_NONE: {
                 return "DUPLEX_MODE_NONE";
@@ -1473,7 +1473,7 @@
         }
     }
 
-    private static String colorModeToString(int colorMode) {
+    static String colorModeToString(int colorMode) {
         switch (colorMode) {
             case COLOR_MODE_MONOCHROME: {
                 return "COLOR_MODE_MONOCHROME";
@@ -1486,7 +1486,7 @@
         }
     }
 
-    private static String orientationToString(int orientation) {
+    static String orientationToString(int orientation) {
         switch (orientation) {
             case ORIENTATION_PORTRAIT: {
                 return "ORIENTATION_PORTRAIT";
@@ -1499,7 +1499,7 @@
         }
     }
 
-    private static String fittingModeToString(int fittingMode) {
+    static String fittingModeToString(int fittingMode) {
         switch (fittingMode) {
             case FITTING_MODE_NONE: {
                 return "FITTING_MODE_NONE";
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 29e8e7c..653ad4b 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 /**
  * This class encapsulates information about a printed document.
@@ -44,6 +45,7 @@
      */
     public static final int CONTENT_TYPE_PHOTO = 1;
 
+    private String mName;
     private int mPageCount;
     private int mContentType;
 
@@ -61,6 +63,7 @@
      * @param Prototype from which to clone.
      */
     private PrintDocumentInfo(PrintDocumentInfo prototype) {
+        mName = prototype.mName;
         mPageCount = prototype.mPageCount;
         mContentType = prototype.mContentType;
     }
@@ -71,11 +74,21 @@
      * @param parcel Data from which to initialize.
      */
     private PrintDocumentInfo(Parcel parcel) {
+        mName = parcel.readString();
         mPageCount = parcel.readInt();
         mContentType = parcel.readInt();
     }
 
     /**
+     * Gets the document name.
+     *
+     * @return The document name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
      * Gets the total number of pages.
      *
      * @return The number of pages.
@@ -106,6 +119,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mName);
         parcel.writeInt(mPageCount);
         parcel.writeInt(mContentType);
     }
@@ -114,6 +128,7 @@
     public int hashCode() {
         final int prime = 31;
         int result = 1;
+        result = prime * result + ((mName != null) ? mName.hashCode() : 0);
         result = prime * result + mContentType;
         result = prime * result + mPageCount;
         return result;
@@ -131,6 +146,9 @@
             return false;
         }
         PrintDocumentInfo other = (PrintDocumentInfo) obj;
+        if (!TextUtils.equals(mName, other.mName)) {
+            return false;
+        }
         if (mContentType != other.mContentType) {
             return false;
         }
@@ -144,17 +162,47 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrintDocumentInfo{");
-        builder.append("pageCount: ").append(mPageCount);
-        builder.append(", contentType: ").append(mContentType);
+        builder.append("name=").append(mName);
+        builder.append(", pageCount=").append(mPageCount);
+        builder.append(", contentType=").append(contentTyepToString(mContentType));
         builder.append("}");
         return builder.toString();
     }
 
+    private String contentTyepToString(int contentType) {
+        switch (contentType) {
+            case CONTENT_TYPE_DOCUMENT: {
+                return "CONTENT_TYPE_DOCUMENT";
+            }
+            case CONTENT_TYPE_PHOTO: {
+                return "CONTENT_TYPE_PHOTO";
+            }
+            default: {
+                return "CONTENT_TYPE_UNKNOWN";
+            }
+        }
+    }
+
     /**
      * Builder for creating an {@link PrintDocumentInfo}.
      */
     public static final class Builder {
-        private final PrintDocumentInfo mPrototype = new PrintDocumentInfo();
+        private final PrintDocumentInfo mPrototype;
+
+        /**
+         * Constructor.
+         *
+         * @param name The document name. Cannot be empty.
+         *
+         * @throws IllegalArgumentException If the name is empty.
+         */
+        public Builder(String name) {
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name cannot be empty");
+            }
+            mPrototype = new PrintDocumentInfo();
+            mPrototype.mName = name;
+        }
 
         /**
          * Sets the total number of pages.
diff --git a/core/java/android/print/FileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
similarity index 81%
rename from core/java/android/print/FileDocumentAdapter.java
rename to core/java/android/print/PrintFileDocumentAdapter.java
index d642a61..4503eda 100644
--- a/core/java/android/print/FileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -36,34 +36,48 @@
 import java.io.OutputStream;
 
 /**
- * Adapter for printing files.
+ * Adapter for printing files. This class could be useful if you
+ * want to print a file and intercept when the system is ready
+ * spooling the data, so you can deleted the file if it is a
+ * temporary one.
  */
-final class FileDocumentAdapter extends PrintDocumentAdapter {
+public final class PrintFileDocumentAdapter extends PrintDocumentAdapter {
 
-    private static final String LOG_TAG = "FileDocumentAdapter";
+    private static final String LOG_TAG = "PrintedFileDocumentAdapter";
 
     private final Context mContext;
 
     private final File mFile;
 
+    private final PrintDocumentInfo mDocumentInfo;
+
     private WriteFileAsyncTask mWriteFileAsyncTask;
 
-    public FileDocumentAdapter(Context context, File file) {
+    /**
+     * Constructor.
+     *
+     * @param context Context for accessing resources.
+     * @param file The file to print.
+     * @param documentInfo The information about the printed file.
+     */
+    public PrintFileDocumentAdapter(Context context, File file,
+            PrintDocumentInfo documentInfo) {
         if (file == null) {
             throw new IllegalArgumentException("File cannot be null!");
         }
+        if (documentInfo == null) {
+            throw new IllegalArgumentException("documentInfo cannot be null!");
+        }
         mContext = context;
         mFile = file;
+        mDocumentInfo = documentInfo;
     }
 
     @Override
     public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
             CancellationSignal cancellationSignal, LayoutResultCallback callback,
             Bundle metadata) {
-        // TODO: When we have a PDF rendering library we should query the page count.
-        PrintDocumentInfo info =  new PrintDocumentInfo.Builder()
-        .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN).create();
-        callback.onLayoutFinished(info, false);
+        callback.onLayoutFinished(mDocumentInfo, false);
     }
 
     @Override
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 096dcd5..2fb4751 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -109,6 +109,9 @@
     /** The unique id of the printer. */
     private PrinterId mPrinterId;
 
+    /** The name of the printer - internally used */
+    private String mPrinterName;
+
     /** The status of the print job. */
     private int mState;
 
@@ -146,6 +149,7 @@
         mId = other.mId;
         mLabel = other.mLabel;
         mPrinterId = other.mPrinterId;
+        mPrinterName = other.mPrinterName;
         mState = other.mState;
         mAppId = other.mAppId;
         mUserId = other.mUserId;
@@ -161,6 +165,7 @@
         mId = parcel.readInt();
         mLabel = parcel.readCharSequence();
         mPrinterId = parcel.readParcelable(null);
+        mPrinterName = parcel.readString();
         mState = parcel.readInt();
         mAppId = parcel.readInt();
         mUserId = parcel.readInt();
@@ -245,6 +250,28 @@
     }
 
     /**
+     * Gets the name of the target printer.
+     *
+     * @return The printer name.
+     *
+     * @hide
+     */
+    public String getPrinterName() {
+        return mPrinterName;
+    }
+
+    /**
+     * Sets the name of the target printer.
+     *
+     * @param printerName The printer name.
+     *
+     * @hide
+     */
+    public void setPrinterName(String printerName) {
+        mPrinterName = printerName;
+    }
+
+    /**
      * Gets the current job state.
      *
      * @return The job state.
@@ -445,6 +472,7 @@
         parcel.writeInt(mId);
         parcel.writeCharSequence(mLabel);
         parcel.writeParcelable(mPrinterId, flags);
+        parcel.writeString(mPrinterName);
         parcel.writeInt(mState);
         parcel.writeInt(mAppId);
         parcel.writeInt(mUserId);
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index c067661..636b9d4 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -161,13 +161,16 @@
      *
      * @param printJobName A name for the new print job.
      * @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.
      *
      * @see PrintJob
      */
-    public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
-        FileDocumentAdapter documentAdapter = new FileDocumentAdapter(mContext, pdfFile);
+    public PrintJob print(String printJobName, File pdfFile, PrintDocumentInfo documentInfo,
+            PrintAttributes attributes) {
+        PrintFileDocumentAdapter documentAdapter = new PrintFileDocumentAdapter(
+                mContext, pdfFile, documentInfo);
         return print(printJobName, documentAdapter, attributes);
     }
 
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.aidl b/core/java/android/print/PrinterCapabilitiesInfo.aidl
new file mode 100644
index 0000000..0f5fb6b
--- /dev/null
+++ b/core/java/android/print/PrinterCapabilitiesInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print;
+
+parcelable PrinterCapabilitiesInfo;
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
new file mode 100644
index 0000000..70b418c
--- /dev/null
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -0,0 +1,972 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class represents the capabilities of a printer.
+ */
+public final class PrinterCapabilitiesInfo implements Parcelable {
+    /**
+     * Undefined default value.
+     *
+     * @hide
+     */
+    public static final int DEFAULT_UNDEFINED = -1;
+
+    private static final int PROPERTY_MEDIA_SIZE = 0;
+    private static final int PROPERTY_RESOLUTION = 1;
+    private static final int PROPERTY_INPUT_TRAY = 2;
+    private static final int PROPERTY_OUTPUT_TRAY = 3;
+    private static final int PROPERTY_DUPLEX_MODE = 4;
+    private static final int PROPERTY_COLOR_MODE = 5;
+    private static final int PROPERTY_FITTING_MODE = 6;
+    private static final int PROPERTY_ORIENTATION = 7;
+    private static final int PROPERTY_COUNT = 8;
+
+    private static final Margins DEFAULT_MARGINS = new Margins(0,  0,  0,  0);
+
+    private Margins mMinMargins = DEFAULT_MARGINS;
+    private List<MediaSize> mMediaSizes;
+    private List<Resolution> mResolutions;
+    private List<Tray> mInputTrays;
+    private List<Tray> mOutputTrays;
+
+    private int mDuplexModes;
+    private int mColorModes;
+    private int mFittingModes;
+    private int mOrientations;
+
+    private final int[] mDefaults = new int[PROPERTY_COUNT];
+    private Margins mDefaultMargins = DEFAULT_MARGINS;
+
+    /**
+     * @hide
+     */
+    public PrinterCapabilitiesInfo() {
+        Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
+    }
+
+    /**
+     * @hide
+     */
+    public PrinterCapabilitiesInfo(PrinterCapabilitiesInfo prototype) {
+        copyFrom(prototype);
+    }
+
+    /**
+     * @hide
+     */
+    public void copyFrom(PrinterCapabilitiesInfo other) {
+        mMinMargins = other.mMinMargins;
+
+        if (other.mMediaSizes != null) {
+            if (mMediaSizes != null) {
+                mMediaSizes.clear();
+                mMediaSizes.addAll(other.mMediaSizes);
+            } else {
+                mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
+            }
+        } else {
+            mMediaSizes = null;
+        }
+
+        if (other.mResolutions != null) {
+            if (mResolutions != null) {
+                mResolutions.clear();
+                mResolutions.addAll(other.mResolutions);
+            } else {
+                mResolutions = new ArrayList<Resolution>(other.mResolutions);
+            }
+        } else {
+            mResolutions = null;
+        }
+
+        if (other.mInputTrays != null) {
+            if (mInputTrays != null) {
+                mInputTrays.clear();
+                mInputTrays.addAll(other.mInputTrays);
+            } else {
+                mInputTrays = new ArrayList<Tray>(other.mInputTrays);
+            }
+        } else {
+            mInputTrays = null;
+        }
+
+        if (other.mOutputTrays != null) {
+            if (mOutputTrays != null) {
+                mOutputTrays.clear();
+                mOutputTrays.addAll(other.mOutputTrays);
+            } else {
+                mOutputTrays = new ArrayList<Tray>(other.mOutputTrays);
+            }
+        } else {
+            mOutputTrays = null;
+        }
+
+        mDuplexModes = other.mDuplexModes;
+        mColorModes = other.mColorModes;
+        mFittingModes = other.mFittingModes;
+        mOrientations = other.mOrientations;
+
+        final int defaultCount = other.mDefaults.length;
+        for (int i = 0; i < defaultCount; i++) {
+            mDefaults[i] = other.mDefaults[i];
+        }
+
+        mDefaultMargins = other.mDefaultMargins;
+    }
+
+    /**
+     * Gets the supported media sizes.
+     *
+     * @return The media sizes.
+     */
+    public List<MediaSize> getMediaSizes() {
+        return mMediaSizes;
+    }
+
+    /**
+     * Gets the supported resolutions.
+     *
+     * @return The resolutions.
+     */
+    public List<Resolution> getResolutions() {
+        return mResolutions;
+    }
+
+    /**
+     * Gets the minimal supported margins.
+     *
+     * @return The minimal margins.
+     */
+    public Margins getMinMargins() {
+        return mMinMargins;
+    }
+
+    /**
+     * Gets the available input trays.
+     *
+     * @return The input trays.
+     */
+    public List<Tray> getInputTrays() {
+        return mInputTrays;
+    }
+
+    /**
+     * Gets the available output trays.
+     *
+     * @return The output trays.
+     */
+    public List<Tray> getOutputTrays() {
+        return mOutputTrays;
+    }
+
+    /**
+     * Gets the supported duplex modes.
+     *
+     * @return The duplex modes.
+     *
+     * @see PrintAttributes#DUPLEX_MODE_NONE
+     * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+     * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+     */
+    public int getDuplexModes() {
+        return mDuplexModes;
+    }
+
+    /**
+     * Gets the supported color modes.
+     *
+     * @return The color modes.
+     *
+     * @see PrintAttributes#COLOR_MODE_COLOR
+     * @see PrintAttributes#COLOR_MODE_MONOCHROME
+     */
+    public int getColorModes() {
+        return mColorModes;
+    }
+
+    /**
+     * Gets the supported fitting modes.
+     *
+     * @return The fitting modes.
+     *
+     * @see PrintAttributes#FITTING_MODE_NONE
+     * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
+     */
+    public int getFittingModes() {
+        return mFittingModes;
+    }
+
+    /**
+     * Gets the supported orientations.
+     *
+     * @return The orientations.
+     *
+     * @see PrintAttributes#ORIENTATION_PORTRAIT
+     * @see PrintAttributes#ORIENTATION_LANDSCAPE
+     */
+    public int getOrientations() {
+        return mOrientations;
+    }
+
+    /**
+     * Gets the default print attributes.
+     *
+     * @param outAttributes The attributes to populated.
+     */
+    public void getDefaults(PrintAttributes outAttributes) {
+        outAttributes.clear();
+
+        outAttributes.setMargins(mDefaultMargins);
+
+        final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
+        if (mediaSizeIndex >= 0) {
+            outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
+        }
+
+        final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
+        if (resolutionIndex >= 0) {
+            outAttributes.setResolution(mResolutions.get(resolutionIndex));
+        }
+
+        final int inputTrayIndex = mDefaults[PROPERTY_INPUT_TRAY];
+        if (inputTrayIndex >= 0) {
+            outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
+        }
+
+        final int outputTrayIndex = mDefaults[PROPERTY_OUTPUT_TRAY];
+        if (outputTrayIndex >= 0) {
+            outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
+        }
+
+        final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
+        if (duplexMode > 0) {
+            outAttributes.setDuplexMode(duplexMode);
+        }
+
+        final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
+        if (colorMode > 0) {
+            outAttributes.setColorMode(colorMode);
+        }
+
+        final int fittingMode = mDefaults[PROPERTY_FITTING_MODE];
+        if (fittingMode > 0) {
+            outAttributes.setFittingMode(fittingMode);
+        }
+
+        final int orientation = mDefaults[PROPERTY_ORIENTATION];
+        if (orientation > 0) {
+            outAttributes.setOrientation(orientation);
+        }
+    }
+
+    private PrinterCapabilitiesInfo(Parcel parcel) {
+        mMinMargins = readMargins(parcel);
+        readMediaSizes(parcel);
+        readResolutions(parcel);
+        mInputTrays = readInputTrays(parcel);
+        mOutputTrays = readOutputTrays(parcel);
+
+        mColorModes = parcel.readInt();
+        mDuplexModes = parcel.readInt();
+        mFittingModes = parcel.readInt();
+        mOrientations = parcel.readInt();
+
+        readDefaults(parcel);
+        mDefaultMargins = readMargins(parcel);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        writeMargins(mMinMargins, parcel);
+        writeMediaSizes(parcel);
+        writeResolutions(parcel);
+        writeInputTrays(parcel);
+        writeOutputTrays(parcel);
+
+        parcel.writeInt(mColorModes);
+        parcel.writeInt(mDuplexModes);
+        parcel.writeInt(mFittingModes);
+        parcel.writeInt(mOrientations);
+
+        writeDefaults(parcel);
+        writeMargins(mDefaultMargins, parcel);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
+        result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
+        result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
+        result = prime * result + ((mInputTrays == null) ? 0 : mInputTrays.hashCode());
+        result = prime * result + ((mOutputTrays == null) ? 0 : mOutputTrays.hashCode());
+        result = prime * result + mColorModes;
+        result = prime * result + mDuplexModes;
+        result = prime * result + mFittingModes;
+        result = prime * result + mOrientations;
+        result = prime * result + Arrays.hashCode(mDefaults);
+        result = prime * result + ((mDefaultMargins == null) ? 0 : mDefaultMargins.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        PrinterCapabilitiesInfo other = (PrinterCapabilitiesInfo) obj;
+        if (mMinMargins == null) {
+            if (other.mMinMargins != null) {
+                return false;
+            }
+        } else if (!mMinMargins.equals(other.mMinMargins)) {
+            return false;
+        }
+        if (mMediaSizes == null) {
+            if (other.mMediaSizes != null) {
+                return false;
+            }
+        } else if (!mMediaSizes.equals(other.mMediaSizes)) {
+            return false;
+        }
+        if (mResolutions == null) {
+            if (other.mResolutions != null) {
+                return false;
+            }
+        } else if (!mResolutions.equals(other.mResolutions)) {
+            return false;
+        }
+        if (mInputTrays == null) {
+            if (other.mInputTrays != null) {
+                return false;
+            }
+        } else if (!mInputTrays.equals(other.mInputTrays)) {
+            return false;
+        }
+        if (mOutputTrays == null) {
+            if (other.mOutputTrays != null) {
+                return false;
+            }
+        } else if (!mOutputTrays.equals(other.mOutputTrays)) {
+            return false;
+        }
+        if (mDuplexModes != other.mDuplexModes) {
+            return false;
+        }
+        if (mColorModes != other.mColorModes) {
+            return false;
+        }
+        if (mFittingModes != other.mFittingModes) {
+            return false;
+        }
+        if (mOrientations != other.mOrientations) {
+            return false;
+        }
+        if (!Arrays.equals(mDefaults, other.mDefaults)) {
+            return false;
+        }
+        if (mDefaultMargins == null) {
+            if (other.mDefaultMargins != null) {
+                return false;
+            }
+        } else if (!mDefaultMargins.equals(other.mDefaultMargins)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("PrinterInfo{");
+        builder.append("minMargins=").append(mMinMargins);
+        builder.append(", mediaSizes=").append(mMediaSizes);
+        builder.append(", resolutions=").append(mResolutions);
+        builder.append(", inputTrays=").append(mInputTrays);
+        builder.append(", outputTrays=").append(mOutputTrays);
+        builder.append(", duplexModes=").append(duplexModesToString());
+        builder.append(", colorModes=").append(colorModesToString());
+        builder.append(", fittingModes=").append(fittingModesToString());
+        builder.append(", orientations=").append(orientationsToString());
+        builder.append("\"}");
+        return builder.toString();
+    }
+
+    private String duplexModesToString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        int duplexModes = mDuplexModes;
+        while (duplexModes != 0) {
+            final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes);
+            duplexModes &= ~duplexMode;
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(PrintAttributes.duplexModeToString(duplexMode));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private String colorModesToString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        int colorModes = mColorModes;
+        while (colorModes != 0) {
+            final int colorMode = 1 << Integer.numberOfTrailingZeros(colorModes);
+            colorModes &= ~colorMode;
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(PrintAttributes.colorModeToString(colorMode));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private String fittingModesToString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        int fittingModes = mFittingModes;
+        while (fittingModes != 0) {
+            final int fittingMode = 1 << Integer.numberOfTrailingZeros(fittingModes);
+            fittingModes &= ~fittingMode;
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(PrintAttributes.fittingModeToString(fittingMode));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private String orientationsToString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        int orientations = mOrientations;
+        while (orientations != 0) {
+            final int orientation = 1 << Integer.numberOfTrailingZeros(orientations);
+            orientations &= ~orientation;
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(PrintAttributes.orientationToString(orientation));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    private void writeMediaSizes(Parcel parcel) {
+        if (mMediaSizes == null) {
+            parcel.writeInt(0);
+            return;
+        }
+        final int mediaSizeCount = mMediaSizes.size();
+        parcel.writeInt(mediaSizeCount);
+        for (int i = 0; i < mediaSizeCount; i++) {
+            mMediaSizes.get(i).writeToParcel(parcel);
+        }
+    }
+
+    private void readMediaSizes(Parcel parcel) {
+        final int mediaSizeCount = parcel.readInt();
+        if (mediaSizeCount > 0 && mMediaSizes == null) {
+            mMediaSizes = new ArrayList<MediaSize>();
+        }
+        for (int i = 0; i < mediaSizeCount; i++) {
+            mMediaSizes.add(MediaSize.createFromParcel(parcel));
+        }
+    }
+
+    private void writeResolutions(Parcel parcel) {
+        if (mResolutions == null) {
+            parcel.writeInt(0);
+            return;
+        }
+        final int resolutionCount = mResolutions.size();
+        parcel.writeInt(resolutionCount);
+        for (int i = 0; i < resolutionCount; i++) {
+            mResolutions.get(i).writeToParcel(parcel);
+        }
+    }
+
+    private void readResolutions(Parcel parcel) {
+        final int resolutionCount = parcel.readInt();
+        if (resolutionCount > 0 && mResolutions == null) {
+            mResolutions = new ArrayList<Resolution>();
+        }
+        for (int i = 0; i < resolutionCount; i++) {
+            mResolutions.add(Resolution.createFromParcel(parcel));
+        }
+    }
+
+    private void writeMargins(Margins margins, Parcel parcel) {
+        if (margins == null) {
+            parcel.writeInt(0);
+        } else {
+            parcel.writeInt(1);
+            margins.writeToParcel(parcel);
+        }
+    }
+
+    private Margins readMargins(Parcel parcel) {
+        return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
+    }
+
+    private void writeInputTrays(Parcel parcel) {
+        if (mInputTrays == null) {
+            parcel.writeInt(0);
+            return;
+        }
+        final int inputTrayCount = mInputTrays.size();
+        parcel.writeInt(inputTrayCount);
+        for (int i = 0; i < inputTrayCount; i++) {
+            mInputTrays.get(i).writeToParcel(parcel);
+        }
+    }
+
+    private List<Tray> readInputTrays(Parcel parcel) {
+        final int inputTrayCount = parcel.readInt();
+        if (inputTrayCount <= 0) {
+            return null;
+        }
+        List<Tray> inputTrays = new ArrayList<Tray>(inputTrayCount);
+        for (int i = 0; i < inputTrayCount; i++) {
+            inputTrays.add(Tray.createFromParcel(parcel));
+        }
+        return inputTrays;
+    }
+
+    private void writeOutputTrays(Parcel parcel) {
+        if (mOutputTrays == null) {
+            parcel.writeInt(0);
+            return;
+        }
+        final int outputTrayCount = mOutputTrays.size();
+        parcel.writeInt(outputTrayCount);
+        for (int i = 0; i < outputTrayCount; i++) {
+            mOutputTrays.get(i).writeToParcel(parcel);
+        }
+    }
+
+    private List<Tray> readOutputTrays(Parcel parcel) {
+        final int outputTrayCount = parcel.readInt();
+        if (outputTrayCount <= 0) {
+            return null;
+        }
+        List<Tray> outputTrays = new ArrayList<Tray>(outputTrayCount);
+        for (int i = 0; i < outputTrayCount; i++) {
+            outputTrays.add(Tray.createFromParcel(parcel));
+        }
+        return outputTrays;
+    }
+
+    private void readDefaults(Parcel parcel) {
+        final int defaultCount = parcel.readInt();
+        for (int i = 0; i < defaultCount; i++) {
+            mDefaults[i] = parcel.readInt();
+        }
+    }
+
+    private void writeDefaults(Parcel parcel) {
+        final int defaultCount = mDefaults.length;
+        parcel.writeInt(defaultCount);
+        for (int i = 0; i < defaultCount; i++) {
+            parcel.writeInt(mDefaults[i]);
+        }
+    }
+
+    /**
+     * Builder for creating of a {@link PrinterInfo}. This class is responsible
+     * to enforce that all required attributes have at least one default value.
+     * In other words, this class creates only well-formed {@link PrinterInfo}s.
+     * <p>
+     * Look at the individual methods for a reference whether a property is
+     * required or if it is optional.
+     * </p>
+     */
+    public static final class Builder {
+        private final PrinterCapabilitiesInfo mPrototype;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param printerId The printer id. Cannot be null.
+         *
+         * @throws IllegalArgumentException If the printer id is null.
+         */
+        public Builder(PrinterId printerId) {
+            if (printerId == null) {
+                throw new IllegalArgumentException("printerId cannot be null.");
+            }
+            mPrototype = new PrinterCapabilitiesInfo();
+        }
+
+        /**
+         * Adds a supported media size.
+         * <p>
+         * <strong>Required:</strong> Yes
+         * </p>
+         *
+         * @param mediaSize A media size.
+         * @param isDefault Whether this is the default.
+         * @return This builder.
+         * @throws IllegalArgumentException If set as default and there
+         *     is already a default.
+         *
+         * @see PrintAttributes.MediaSize
+         */
+        public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
+            if (mPrototype.mMediaSizes == null) {
+                mPrototype.mMediaSizes = new ArrayList<MediaSize>();
+            }
+            final int insertionIndex = mPrototype.mMediaSizes.size();
+            mPrototype.mMediaSizes.add(mediaSize);
+            if (isDefault) {
+                throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
+                mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
+            }
+            return this;
+        }
+
+        /**
+         * Adds a supported resolution.
+         * <p>
+         * <strong>Required:</strong> Yes
+         * </p>
+         *
+         * @param resolution A resolution.
+         * @param isDefault Whether this is the default.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If set as default and there
+         *     is already a default.
+         *
+         * @see PrintAttributes.Resolution
+         */
+        public Builder addResolution(Resolution resolution, boolean isDefault) {
+            if (mPrototype.mResolutions == null) {
+                mPrototype.mResolutions = new ArrayList<Resolution>();
+            }
+            final int insertionIndex = mPrototype.mResolutions.size();
+            mPrototype.mResolutions.add(resolution);
+            if (isDefault) {
+                throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
+                mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the minimal margins.
+         * <p>
+         * <strong>Required:</strong> No
+         * </p>
+         *
+         * @param margins The margins.
+         * @param defaultMargins The default margins.
+         * @return This builder.
+         *
+         * @see PrintAttributes.Margins
+         */
+        public Builder setMinMargins(Margins margins, Margins defaultMargins) {
+            if (margins.getLeftMils() > defaultMargins.getLeftMils()
+                    || margins.getTopMils() > defaultMargins.getTopMils()
+                    || margins.getRightMils() < defaultMargins.getRightMils()
+                    || margins.getBottomMils() < defaultMargins.getBottomMils()) {
+                throw new IllegalArgumentException("Default margins"
+                    + " cannot be outside of the min margins.");
+            }
+            mPrototype.mMinMargins = margins;
+            mPrototype.mDefaultMargins = defaultMargins;
+            return this;
+        }
+
+        /**
+         * Adds an input tray.
+         * <p>
+         * <strong>Required:</strong> No
+         * </p>
+         *
+         * @param inputTray A tray.
+         * @param isDefault Whether this is the default.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If set as default and there
+         *     is already a default.
+         *
+         * @see PrintAttributes.Tray
+         */
+        public Builder addInputTray(Tray inputTray, boolean isDefault) {
+            if (mPrototype.mInputTrays == null) {
+                mPrototype.mInputTrays = new ArrayList<Tray>();
+            }
+            final int insertionIndex = mPrototype.mInputTrays.size();
+            mPrototype.mInputTrays.add(inputTray);
+            if (isDefault) {
+                throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
+                mPrototype.mDefaults[PROPERTY_INPUT_TRAY] = insertionIndex;
+            }
+            return this;
+        }
+
+        /**
+         * Adds an output tray.
+         * <p>
+         * <strong>Required:</strong> No
+         * </p>
+         *
+         * @param outputTray A tray.
+         * @param isDefault Whether this is the default.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If set as default and there
+         *     is already a default.
+         *
+         * @see PrintAttributes.Tray
+         */
+        public Builder addOutputTray(Tray outputTray, boolean isDefault) {
+            if (mPrototype.mOutputTrays == null) {
+                mPrototype.mOutputTrays = new ArrayList<Tray>();
+            }
+            final int insertionIndex = mPrototype.mOutputTrays.size();
+            mPrototype.mOutputTrays.add(outputTray);
+            if (isDefault) {
+                throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
+                mPrototype.mDefaults[PROPERTY_OUTPUT_TRAY] = insertionIndex;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the color modes.
+         * <p>
+         * <strong>Required:</strong> Yes
+         * </p>
+         *
+         * @param colorModes The color mode bit mask.
+         * @param defaultColorMode The default color mode.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If color modes contains an invalid
+         *         mode bit or if the default color mode is invalid.
+         *
+         * @see PrintAttributes#COLOR_MODE_COLOR
+         * @see PrintAttributes#COLOR_MODE_MONOCHROME
+         */
+        public Builder setColorModes(int colorModes, int defaultColorMode) {
+            int currentModes = colorModes;
+            while (currentModes > 0) {
+                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
+                currentModes &= ~currentMode;
+                PrintAttributes.enforceValidColorMode(currentMode);
+            }
+            if ((colorModes & defaultColorMode) == 0) {
+                throw new IllegalArgumentException("Default color mode not in color modes.");
+            }
+            PrintAttributes.enforceValidColorMode(colorModes);
+            mPrototype.mColorModes = colorModes;
+            mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
+            return this;
+        }
+
+        /**
+         * Set the duplex modes.
+         * <p>
+         * <strong>Required:</strong> No
+         * </p>
+         *
+         * @param duplexModes The duplex mode bit mask.
+         * @param defaultDuplexMode The default duplex mode.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If duplex modes contains an invalid
+         *         mode bit or if the default duplex mode is invalid.
+         *
+         * @see PrintAttributes#DUPLEX_MODE_NONE
+         * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+         * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+         */
+        public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
+            int currentModes = duplexModes;
+            while (currentModes > 0) {
+                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
+                currentModes &= ~currentMode;
+                PrintAttributes.enforceValidDuplexMode(currentMode);
+            }
+            if ((duplexModes & defaultDuplexMode) == 0) {
+                throw new IllegalArgumentException("Default duplex mode not in duplex modes.");
+            }
+            PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
+            mPrototype.mDuplexModes = duplexModes;
+            mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
+            return this;
+        }
+
+        /**
+         * Sets the fitting modes.
+         * <p>
+         * <strong>Required:</strong> No
+         * </p>
+         *
+         * @param fittingModes The fitting mode bit mask.
+         * @param defaultFittingMode The default fitting mode.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If fitting modes contains an invalid
+         *         mode bit or if the default fitting mode is invalid.
+         *
+         * @see PrintAttributes#FITTING_MODE_NONE
+         * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
+         */
+        public Builder setFittingModes(int fittingModes, int defaultFittingMode) {
+            int currentModes = fittingModes;
+            while (currentModes > 0) {
+                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
+                currentModes &= ~currentMode;
+                PrintAttributes.enfoceValidFittingMode(currentMode);
+            }
+            if ((fittingModes & defaultFittingMode) == 0) {
+                throw new IllegalArgumentException("Default fitting mode not in fiting modes.");
+            }
+            PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
+            mPrototype.mFittingModes = fittingModes;
+            mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
+            return this;
+        }
+
+        /**
+         * Sets the orientations.
+         * <p>
+         * <strong>Required:</strong> Yes
+         * </p>
+         *
+         * @param orientations The orientation bit mask.
+         * @param defaultOrientation The default orientation.
+         * @return This builder.
+         *
+         * @throws IllegalArgumentException If orientations contains an invalid
+         *         mode bit or if the default orientation is invalid.
+         *
+         * @see PrintAttributes#ORIENTATION_PORTRAIT
+         * @see PrintAttributes#ORIENTATION_LANDSCAPE
+         */
+        public Builder setOrientations(int orientations, int defaultOrientation) {
+            int currentOrientaions = orientations;
+            while (currentOrientaions > 0) {
+                final int currentOrnt = (1 << Integer.numberOfTrailingZeros(currentOrientaions));
+                currentOrientaions &= ~currentOrnt;
+                PrintAttributes.enforceValidOrientation(currentOrnt);
+            }
+            if ((orientations & defaultOrientation) == 0) {
+                throw new IllegalArgumentException("Default orientation not in orientations.");
+            }
+            PrintAttributes.enforceValidOrientation(defaultOrientation);
+            mPrototype.mOrientations = orientations;
+            mPrototype.mDefaults[PROPERTY_ORIENTATION] = defaultOrientation;
+            return this;
+        }
+
+        /**
+         * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all
+         * required properties have need specified. See individual methods
+         * in this class for reference about required attributes.
+         *
+         * @return A new {@link PrinterCapabilitiesInfo}.
+         *
+         * @throws IllegalStateException If a required attribute was not specified.
+         */
+        public PrinterCapabilitiesInfo create() {
+            if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) {
+                throw new IllegalStateException("No media size specified.");
+            }
+            if (mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] == DEFAULT_UNDEFINED) {
+                throw new IllegalStateException("No default media size specified.");
+            }
+            if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) {
+                throw new IllegalStateException("No resolution specified.");
+            }
+            if (mPrototype.mDefaults[PROPERTY_RESOLUTION] == DEFAULT_UNDEFINED) {
+                throw new IllegalStateException("No default resolution specified.");
+            }
+            if (mPrototype.mColorModes == 0) {
+                throw new IllegalStateException("No color mode specified.");
+            }
+            if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) {
+                throw new IllegalStateException("No default color mode specified.");
+            }
+            if (mPrototype.mOrientations == 0) {
+                throw new IllegalStateException("No oprientation specified.");
+            }
+            if (mPrototype.mDefaults[PROPERTY_ORIENTATION] == DEFAULT_UNDEFINED) {
+                throw new IllegalStateException("No default orientation specified.");
+            }
+            if (mPrototype.mMinMargins == null) {
+                mPrototype.mMinMargins  = new Margins(0, 0, 0, 0);
+            }
+            if (mPrototype.mDefaultMargins == null) {
+                mPrototype.mDefaultMargins = mPrototype.mMinMargins;
+            }
+            return new PrinterCapabilitiesInfo(mPrototype);
+        }
+
+        private void throwIfDefaultAlreadySpecified(int propertyIndex) {
+            if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
+                throw new IllegalArgumentException("Default already specified.");
+            }
+        }
+    }
+
+    public static final Parcelable.Creator<PrinterCapabilitiesInfo> CREATOR =
+            new Parcelable.Creator<PrinterCapabilitiesInfo>() {
+        @Override
+        public PrinterCapabilitiesInfo createFromParcel(Parcel parcel) {
+            return new PrinterCapabilitiesInfo(parcel);
+        }
+
+        @Override
+        public PrinterCapabilitiesInfo[] newArray(int size) {
+            return new PrinterCapabilitiesInfo[size];
+        }
+    };
+}
+
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 8462736..a3f3b2bf 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -28,24 +28,24 @@
 
     private final ComponentName mServiceName;
 
-    private final String mPrinterName;
+    private final String mLocalId;
 
     /**
      * Creates a new instance.
      *
      * @param serviceName The managing print service.
-     * @param printerName The unique name within the managing service.
+     * @param localId The locally unique id within the managing service.
      *
      * @hide
      */
-    public PrinterId(ComponentName serviceName, String printerName) {
+    public PrinterId(ComponentName serviceName, String localId) {
         mServiceName = serviceName;
-        mPrinterName = printerName;
+        mLocalId = localId;
     }
 
     private PrinterId(Parcel parcel) {
         mServiceName = parcel.readParcelable(null);
-        mPrinterName = parcel.readString();
+        mLocalId = parcel.readString();
     }
 
     /**
@@ -60,13 +60,13 @@
     }
 
     /**
-     * Gets the name of this printer which is unique in the context
+     * Gets the id of this printer which is unique in the context
      * of the print service that manages it.
      *
      * @return The printer name.
      */
-    public String getPrinterName() {
-        return mPrinterName;
+    public String getLocalId() {
+        return mLocalId;
     }
 
     @Override
@@ -77,7 +77,7 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mServiceName, flags);
-        parcel.writeString(mPrinterName);
+        parcel.writeString(mLocalId);
     }
 
     @Override
@@ -99,7 +99,7 @@
         } else if (!mServiceName.equals(other.mServiceName)) {
             return false;
         }
-        if (!TextUtils.equals(mPrinterName, other.mPrinterName)) {
+        if (!TextUtils.equals(mLocalId, other.mLocalId)) {
             return false;
         }
         return true;
@@ -111,7 +111,7 @@
         int hashCode = 1;
         hashCode = prime * hashCode + ((mServiceName != null)
                 ? mServiceName.hashCode() : 1);
-        hashCode = prime * hashCode + mPrinterName.hashCode();
+        hashCode = prime * hashCode + mLocalId.hashCode();
         return hashCode;
     }
 
@@ -119,9 +119,8 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrinterId{");
-        builder.append(mServiceName.flattenToString());
-        builder.append(":");
-        builder.append(mPrinterName);
+        builder.append("serviceName=").append(mServiceName.flattenToString());
+        builder.append(", localId=").append(mLocalId);
         builder.append('}');
         return builder.toString();
     }
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 10cecca..ac782a8 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -18,67 +18,30 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintAttributes.Tray;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import android.text.TextUtils;
 
 /**
- * This class represents the description of a printer. A description
- * contains the printer id, human readable name, status, and available
- * options for various printer capabilities, such as media size, etc.
+ * This class represents the description of a printer.
  */
 public final class PrinterInfo implements Parcelable {
-    /**
-     * Undefined default value.
-     *
-     * @hide
-     */
-    public static final int DEFAULT_UNDEFINED = -1;
-
-    private static final int PROPERTY_MEDIA_SIZE = 0;
-    private static final int PROPERTY_RESOLUTION = 1;
-    private static final int PROPERTY_INPUT_TRAY = 2;
-    private static final int PROPERTY_OUTPUT_TRAY = 3;
-    private static final int PROPERTY_DUPLEX_MODE = 4;
-    private static final int PROPERTY_COLOR_MODE = 5;
-    private static final int PROPERTY_FITTING_MODE = 6;
-    private static final int PROPERTY_ORIENTATION = 7;
-    private static final int PROPERTY_COUNT = 8;
 
     /** Printer status: the printer is ready to print. */
     public static final int STATUS_READY = 1;
 
-    private static final Margins DEFAULT_MARGINS = new Margins(0,  0,  0,  0);
-
     // TODO: Add printer status constants.
 
     private PrinterId mId;
+
+    private String mName;
+
     private int mStatus;
 
-    private Margins mMinMargins = DEFAULT_MARGINS;
-    private List<MediaSize> mMediaSizes;
-    private List<Resolution> mResolutions;
-    private List<Tray> mInputTrays;
-    private List<Tray> mOutputTrays;
+    private String mDescription;
 
-    private int mDuplexModes;
-    private int mColorModes;
-    private int mFittingModes;
-    private int mOrientations;
+    private PrinterCapabilitiesInfo mCapabilities;
 
-    private final int[] mDefaults = new int[PROPERTY_COUNT];
-    private Margins mDefaultMargins = DEFAULT_MARGINS;
-
-    /**
-     * @hide
-     */
-    public PrinterInfo() {
-        Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
+    private PrinterInfo() {
+        /* do nothing */
     }
 
     private PrinterInfo(PrinterInfo prototype) {
@@ -90,63 +53,18 @@
      */
     public void copyFrom(PrinterInfo other) {
         mId = other.mId;
+        mName = other.mName;
         mStatus = other.mStatus;
-
-        mMinMargins = other.mMinMargins;
-        if (other.mMediaSizes != null) {
-            if (mMediaSizes != null) {
-                mMediaSizes.clear();
-                mMediaSizes.addAll(other.mMediaSizes);
+        mDescription = other.mDescription;
+        if (other.mCapabilities != null) {
+            if (mCapabilities != null) {
+                mCapabilities.copyFrom(other.mCapabilities);
             } else {
-                mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
+                mCapabilities = new PrinterCapabilitiesInfo(other.mCapabilities);
             }
         } else {
-            mMediaSizes = null;
+            mCapabilities = null;
         }
-
-        if (other.mResolutions != null) {
-            if (mResolutions != null) {
-                mResolutions.clear();
-                mResolutions.addAll(other.mResolutions);
-            } else {
-                mResolutions = new ArrayList<Resolution>(other.mResolutions);
-            }
-        } else {
-            mResolutions = null;
-        }
-
-        if (other.mInputTrays != null) {
-            if (mInputTrays != null) {
-                mInputTrays.clear();
-                mInputTrays.addAll(other.mInputTrays);
-            } else {
-                mInputTrays = new ArrayList<Tray>(other.mInputTrays);
-            }
-        } else {
-            mInputTrays = null;
-        }
-
-        if (other.mOutputTrays != null) {
-            if (mOutputTrays != null) {
-                mOutputTrays.clear();
-                mOutputTrays.addAll(other.mOutputTrays);
-            } else {
-                mOutputTrays = new ArrayList<Tray>(other.mOutputTrays);
-            }
-        } else {
-            mOutputTrays = null;
-        }
-
-        mDuplexModes = other.mDuplexModes;
-        mColorModes = other.mColorModes;
-        mFittingModes = other.mFittingModes;
-        mOrientations = other.mOrientations;
-
-        final int defaultCount = other.mDefaults.length;
-        for (int i = 0; i < defaultCount; i++) {
-            mDefaults[i] = other.mDefaults[i];
-        }
-        mDefaultMargins = other.mDefaultMargins;
     }
 
     /**
@@ -159,7 +77,16 @@
     }
 
     /**
-     * Gets the status of the printer.
+     * Get the printer name.
+     *
+     * @return The printer name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Gets the printer status.
      *
      * @return The status.
      */
@@ -168,184 +95,29 @@
     }
 
     /**
-     * Gets the supported media sizes.
+     * Gets the  printer description.
      *
-     * @return The supported media sizes.
+     * @return The description.
      */
-    public List<MediaSize> getMediaSizes() {
-        return mMediaSizes;
+    public String getDescription() {
+        return mDescription;
     }
 
     /**
-     * Gets the supported resolutions.
+     * Gets the printer capabilities.
      *
-     * @return The supported resolutions.
+     * @return The capabilities.
      */
-    public List<Resolution> getResolutions() {
-        return mResolutions;
-    }
-
-    /**
-     * Gets the minimal supported margins.
-     *
-     * @return The minimal margins.
-     */
-    public Margins getMinMargins() {
-        return mMinMargins;
-    }
-
-    /**
-     * Gets the available input trays.
-     *
-     * @return The input trays.
-     */
-    public List<Tray> getInputTrays() {
-        return mInputTrays;
-    }
-
-    /**
-     * Gets the available output trays.
-     *
-     * @return The output trays.
-     */
-    public List<Tray> getOutputTrays() {
-        return mOutputTrays;
-    }
-
-    /**
-     * Gets the supported duplex modes.
-     *
-     * @return The duplex modes.
-     *
-     * @see PrintAttributes#DUPLEX_MODE_NONE
-     * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
-     * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
-     */
-    public int getDuplexModes() {
-        return mDuplexModes;
-    }
-
-    /**
-     * Gets the supported color modes.
-     *
-     * @return The color modes.
-     *
-     * @see PrintAttributes#COLOR_MODE_COLOR
-     * @see PrintAttributes#COLOR_MODE_MONOCHROME
-     */
-    public int getColorModes() {
-        return mColorModes;
-    }
-
-    /**
-     * Gets the supported fitting modes.
-     *
-     * @return The fitting modes.
-     *
-     * @see PrintAttributes#FITTING_MODE_NONE
-     * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
-     */
-    public int getFittingModes() {
-        return mFittingModes;
-    }
-
-    /**
-     * Gets the supported orientations.
-     *
-     * @return The orientations.
-     *
-     * @see PrintAttributes#ORIENTATION_PORTRAIT
-     * @see PrintAttributes#ORIENTATION_LANDSCAPE
-     */
-    public int getOrientations() {
-        return mOrientations;
-    }
-
-    /**
-     * Gets the default print attributes.
-     *
-     * @param outAttributes The attributes to populated.
-     */
-    public void getDefaults(PrintAttributes outAttributes) {
-        outAttributes.clear();
-
-        outAttributes.setMargins(mDefaultMargins);
-
-        final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
-        if (mediaSizeIndex >= 0) {
-            outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
-        }
-
-        final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
-        if (resolutionIndex >= 0) {
-            outAttributes.setResolution(mResolutions.get(resolutionIndex));
-        }
-
-        final int inputTrayIndex = mDefaults[PROPERTY_INPUT_TRAY];
-        if (inputTrayIndex >= 0) {
-            outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
-        }
-
-        final int outputTrayIndex = mDefaults[PROPERTY_OUTPUT_TRAY];
-        if (outputTrayIndex >= 0) {
-            outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
-        }
-
-        final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
-        if (duplexMode > 0) {
-            outAttributes.setDuplexMode(duplexMode);
-        }
-
-        final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
-        if (colorMode > 0) {
-            outAttributes.setColorMode(mColorModes & colorMode);
-        }
-
-        final int fittingMode = mDefaults[PROPERTY_FITTING_MODE];
-        if (fittingMode > 0) {
-            outAttributes.setFittingMode(fittingMode);
-        }
-
-        final int orientation = mDefaults[PROPERTY_ORIENTATION];
-        if (orientation > 0) {
-            outAttributes.setOrientation(orientation);
-        }
-    }
-
-    /**
-     * Gets whether this printer info is fully-populated, i.e. whether
-     * all required attributes are specified. See the {@link Builder}
-     * documentation for which attributes are required.
-     *
-     * @return Whether this info has all required attributes.
-     */
-    public boolean hasAllRequiredAttributes() {
-        return (mMediaSizes != null && !mMediaSizes.isEmpty()
-                && mResolutions != null && !mResolutions.isEmpty()
-                && mColorModes != 0 || mOrientations != 0
-                && mDefaults[PROPERTY_MEDIA_SIZE] != DEFAULT_UNDEFINED
-                && mDefaults[PROPERTY_RESOLUTION] != DEFAULT_UNDEFINED
-                && mDefaults[PROPERTY_COLOR_MODE] != DEFAULT_UNDEFINED
-                && mDefaults[PROPERTY_ORIENTATION] != DEFAULT_UNDEFINED);
+    public PrinterCapabilitiesInfo getCapabilities() {
+        return mCapabilities;
     }
 
     private PrinterInfo(Parcel parcel) {
         mId = parcel.readParcelable(null);
+        mName = parcel.readString();
         mStatus = parcel.readInt();
-
-        mMinMargins = readMargins(parcel);
-        readMediaSizes(parcel);
-        readResolutions(parcel);
-        mInputTrays = readInputTrays(parcel);
-        mOutputTrays = readOutputTrays(parcel);
-
-        mColorModes = parcel.readInt();
-        mDuplexModes = parcel.readInt();
-        mFittingModes = parcel.readInt();
-        mOrientations = parcel.readInt();
-
-        readDefaults(parcel);
-        mDefaultMargins = readMargins(parcel);
+        mDescription = parcel.readString();
+        mCapabilities = parcel.readParcelable(null);
     }
 
     @Override
@@ -356,40 +128,21 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mId, flags);
+        parcel.writeString(mName);
         parcel.writeInt(mStatus);
-
-        writeMargins(mMinMargins, parcel);
-        writeMediaSizes(parcel);
-        writeResolutions(parcel);
-        writeInputTrays(parcel);
-        writeOutputTrays(parcel);
-
-        parcel.writeInt(mColorModes);
-        parcel.writeInt(mDuplexModes);
-        parcel.writeInt(mFittingModes);
-        parcel.writeInt(mOrientations);
-
-        writeDefaults(parcel);
-        writeMargins(mDefaultMargins, parcel);
+        parcel.writeString(mDescription);
+        parcel.writeParcelable(mCapabilities, flags);
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+        result = prime * result + ((mId != null) ? mId.hashCode() : 0);
+        result = prime * result + ((mName != null) ? mName.hashCode() : 0);
         result = prime * result + mStatus;
-        result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
-        result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
-        result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
-        result = prime * result + ((mInputTrays == null) ? 0 : mInputTrays.hashCode());
-        result = prime * result + ((mOutputTrays == null) ? 0 : mOutputTrays.hashCode());
-        result = prime * result + mDuplexModes;
-        result = prime * result + mColorModes;
-        result = prime * result + mFittingModes;
-        result = prime * result + mOrientations;
-        result = prime * result + Arrays.hashCode(mDefaults);
-        result = prime * result + ((mDefaultMargins == null) ? 0 : mDefaultMargins.hashCode());
+        result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0);
+        result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0);
         return result;
     }
 
@@ -412,64 +165,20 @@
         } else if (!mId.equals(other.mId)) {
             return false;
         }
+        if (!TextUtils.equals(mName, other.mName)) {
+            return false;
+        }
         if (mStatus != other.mStatus) {
             return false;
         }
-        if (mMinMargins == null) {
-            if (other.mMinMargins != null) {
+        if (!TextUtils.equals(mDescription, other.mDescription)) {
+            return false;
+        }
+        if (mCapabilities == null) {
+            if (other.mCapabilities != null) {
                 return false;
             }
-        } else if (!mMinMargins.equals(other.mMinMargins)) {
-            return false;
-        }
-        if (mMediaSizes == null) {
-            if (other.mMediaSizes != null) {
-                return false;
-            }
-        } else if (!mMediaSizes.equals(other.mMediaSizes)) {
-            return false;
-        }
-        if (mResolutions == null) {
-            if (other.mResolutions != null) {
-                return false;
-            }
-        } else if (!mResolutions.equals(other.mResolutions)) {
-            return false;
-        }
-        if (mInputTrays == null) {
-            if (other.mInputTrays != null) {
-                return false;
-            }
-        } else if (!mInputTrays.equals(other.mInputTrays)) {
-            return false;
-        }
-        if (mOutputTrays == null) {
-            if (other.mOutputTrays != null) {
-                return false;
-            }
-        } else if (!mOutputTrays.equals(other.mOutputTrays)) {
-            return false;
-        }
-        if (mDuplexModes != other.mDuplexModes) {
-            return false;
-        }
-        if (mColorModes != other.mColorModes) {
-            return false;
-        }
-        if (mFittingModes != other.mFittingModes) {
-            return false;
-        }
-        if (mOrientations != other.mOrientations) {
-            return false;
-        }
-        if (!Arrays.equals(mDefaults, other.mDefaults)) {
-            return false;
-        }
-        if (mDefaultMargins == null) {
-            if (other.mDefaultMargins != null) {
-                return false;
-            }
-        } else if (!mDefaultMargins.equals(other.mDefaultMargins)) {
+        } else if (!mCapabilities.equals(other.mCapabilities)) {
             return false;
         }
         return true;
@@ -479,434 +188,83 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrinterInfo{");
-        builder.append(mId).append(", \"");
+        builder.append("id=").append(mId);
+        builder.append(", name=").append(mName);
+        builder.append(", status=").append(mStatus);
+        builder.append(", description=").append(mDescription);
+        builder.append(", capabilities=").append(mCapabilities);
         builder.append("\"}");
         return builder.toString();
     }
 
-    private void writeMediaSizes(Parcel parcel) {
-        if (mMediaSizes == null) {
-            parcel.writeInt(0);
-            return;
-        }
-        final int mediaSizeCount = mMediaSizes.size();
-        parcel.writeInt(mediaSizeCount);
-        for (int i = 0; i < mediaSizeCount; i++) {
-            mMediaSizes.get(i).writeToParcel(parcel);
-        }
-    }
-
-    private void readMediaSizes(Parcel parcel) {
-        final int mediaSizeCount = parcel.readInt();
-        if (mediaSizeCount > 0 && mMediaSizes == null) {
-            mMediaSizes = new ArrayList<MediaSize>();
-        }
-        for (int i = 0; i < mediaSizeCount; i++) {
-            mMediaSizes.add(MediaSize.createFromParcel(parcel));
-        }
-    }
-
-    private void writeResolutions(Parcel parcel) {
-        if (mResolutions == null) {
-            parcel.writeInt(0);
-            return;
-        }
-        final int resolutionCount = mResolutions.size();
-        parcel.writeInt(resolutionCount);
-        for (int i = 0; i < resolutionCount; i++) {
-            mResolutions.get(i).writeToParcel(parcel);
-        }
-    }
-
-    private void readResolutions(Parcel parcel) {
-        final int resolutionCount = parcel.readInt();
-        if (resolutionCount > 0 && mResolutions == null) {
-            mResolutions = new ArrayList<Resolution>();
-        }
-        for (int i = 0; i < resolutionCount; i++) {
-            mResolutions.add(Resolution.createFromParcel(parcel));
-        }
-    }
-
-    private void writeMargins(Margins margins, Parcel parcel) {
-        if (margins == null) {
-            parcel.writeInt(0);
-        } else {
-            parcel.writeInt(1);
-            margins.writeToParcel(parcel);
-        }
-    }
-
-    private Margins readMargins(Parcel parcel) {
-        return (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
-    }
-
-    private void writeInputTrays(Parcel parcel) {
-        if (mInputTrays == null) {
-            parcel.writeInt(0);
-            return;
-        }
-        final int inputTrayCount = mInputTrays.size();
-        parcel.writeInt(inputTrayCount);
-        for (int i = 0; i < inputTrayCount; i++) {
-            mInputTrays.get(i).writeToParcel(parcel);
-        }
-    }
-
-    private List<Tray> readInputTrays(Parcel parcel) {
-        final int inputTrayCount = parcel.readInt();
-        if (inputTrayCount <= 0) {
-            return null;
-        }
-        List<Tray> inputTrays = new ArrayList<Tray>(inputTrayCount);
-        for (int i = 0; i < inputTrayCount; i++) {
-            inputTrays.add(Tray.createFromParcel(parcel));
-        }
-        return inputTrays;
-    }
-
-    private void writeOutputTrays(Parcel parcel) {
-        if (mOutputTrays == null) {
-            parcel.writeInt(0);
-            return;
-        }
-        final int outputTrayCount = mOutputTrays.size();
-        parcel.writeInt(outputTrayCount);
-        for (int i = 0; i < outputTrayCount; i++) {
-            mOutputTrays.get(i).writeToParcel(parcel);
-        }
-    }
-
-    private List<Tray> readOutputTrays(Parcel parcel) {
-        final int outputTrayCount = parcel.readInt();
-        if (outputTrayCount <= 0) {
-            return null;
-        }
-        List<Tray> outputTrays = new ArrayList<Tray>(outputTrayCount);
-        for (int i = 0; i < outputTrayCount; i++) {
-            outputTrays.add(Tray.createFromParcel(parcel));
-        }
-        return outputTrays;
-    }
-
-    private void readDefaults(Parcel parcel) {
-        final int defaultCount = parcel.readInt();
-        for (int i = 0; i < defaultCount; i++) {
-            mDefaults[i] = parcel.readInt();
-        }
-    }
-
-    private void writeDefaults(Parcel parcel) {
-        final int defaultCount = mDefaults.length;
-        parcel.writeInt(defaultCount);
-        for (int i = 0; i < defaultCount; i++) {
-            parcel.writeInt(mDefaults[i]);
-        }
-    }
-
     /**
-     * Builder for creating of a {@link PrinterInfo}. This class is responsible
-     * to enforce that all required attributes have at least one default value.
-     * In other words, this class creates only well-formed {@link PrinterInfo}s.
-     * <p>
-     * Look at the individual methods for a reference whether a property is
-     * required or if it is optional.
-     * </p>
+     * Builder for creating of a {@link PrinterInfo}.
      */
     public static final class Builder {
         private final PrinterInfo mPrototype;
 
         /**
-         * Creates a new instance.
+         * Constructor.
          *
          * @param printerId The printer id. Cannot be null.
-         *
-         * @throws IllegalArgumentException If the printer id is null.
+         * @param name The printer name. Cannot be empty.
+         * @param status The printer status. Must be a valid status.
          */
-        public Builder(PrinterId printerId) {
+        public Builder(PrinterId printerId, String name, int status) {
             if (printerId == null) {
                 throw new IllegalArgumentException("printerId cannot be null.");
             }
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name cannot be empty.");
+            }
+            if (!isValidStatus(status)) {
+                throw new IllegalArgumentException("status is invalid.");
+            }
             mPrototype = new PrinterInfo();
             mPrototype.mId = printerId;
-        }
-
-        /**
-         * Sets the printer status.
-         * <p>
-         * <strong>Required:</strong> Yes
-         * </p>
-         *
-         * @param status The status.
-         * @return This builder.
-         */
-        public Builder setStatus(int status) {
+            mPrototype.mName = name;
             mPrototype.mStatus = status;
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param prototype Prototype from which to start building.
+         */
+        public Builder(PrinterInfo prototype) {
+            mPrototype = prototype;
+        }
+
+        /**
+         * Sets the printer name.
+         *
+         * @param name The name.
+         * @return This builder.
+         */
+        public Builder setName(String name) {
+            mPrototype.mName = name;
             return this;
         }
 
         /**
-         * Adds a supported media size.
-         * <p>
-         * <strong>Required:</strong> Yes
-         * </p>
+         * Sets the printer description.
          *
-         * @param mediaSize A media size.
-         * @param isDefault Whether this is the default.
+         * @param description The description.
          * @return This builder.
-         * @throws IllegalArgumentException If set as default and there
-         *     is already a default.
-         *
-         * @see PrintAttributes.MediaSize
          */
-        public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
-            if (mPrototype.mMediaSizes == null) {
-                mPrototype.mMediaSizes = new ArrayList<MediaSize>();
-            }
-            final int insertionIndex = mPrototype.mMediaSizes.size();
-            mPrototype.mMediaSizes.add(mediaSize);
-            if (isDefault) {
-                throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
-                mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
-            }
+        public Builder setDescription(String description) {
+            mPrototype.mDescription = description;
             return this;
         }
 
         /**
-         * Adds a supported resolution.
-         * <p>
-         * <strong>Required:</strong> Yes
-         * </p>
+         * Sets the printer capabilities.
          *
-         * @param resolution A resolution.
-         * @param isDefault Whether this is the default.
+         * @param capabilities The capabilities.
          * @return This builder.
-         *
-         * @throws IllegalArgumentException If set as default and there
-         *     is already a default.
-         *
-         * @see PrintAttributes.Resolution
          */
-        public Builder addResolution(Resolution resolution, boolean isDefault) {
-            if (mPrototype.mResolutions == null) {
-                mPrototype.mResolutions = new ArrayList<Resolution>();
-            }
-            final int insertionIndex = mPrototype.mResolutions.size();
-            mPrototype.mResolutions.add(resolution);
-            if (isDefault) {
-                throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
-                mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the minimal margins.
-         * <p>
-         * <strong>Required:</strong> No
-         * </p>
-         *
-         * @param margins The margins.
-         * @param defaultMargins The default margins.
-         * @return This builder.
-         *
-         * @see PrintAttributes.Margins
-         */
-        public Builder setMinMargins(Margins margins, Margins defaultMargins) {
-            if (margins.getLeftMils() > defaultMargins.getLeftMils()
-                    || margins.getTopMils() > defaultMargins.getTopMils()
-                    || margins.getRightMils() < defaultMargins.getRightMils()
-                    || margins.getBottomMils() < defaultMargins.getBottomMils()) {
-                throw new IllegalArgumentException("Default margins"
-                    + " cannot be outside of the min margins.");
-            }
-            mPrototype.mMinMargins = margins;
-            mPrototype.mDefaultMargins = defaultMargins;
-            return this;
-        }
-
-        /**
-         * Adds an input tray.
-         * <p>
-         * <strong>Required:</strong> No
-         * </p>
-         *
-         * @param inputTray A tray.
-         * @param isDefault Whether this is the default.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If set as default and there
-         *     is already a default.
-         *
-         * @see PrintAttributes.Tray
-         */
-        public Builder addInputTray(Tray inputTray, boolean isDefault) {
-            if (mPrototype.mInputTrays == null) {
-                mPrototype.mInputTrays = new ArrayList<Tray>();
-            }
-            final int insertionIndex = mPrototype.mInputTrays.size();
-            mPrototype.mInputTrays.add(inputTray);
-            if (isDefault) {
-                throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
-                mPrototype.mDefaults[PROPERTY_INPUT_TRAY] = insertionIndex;
-            }
-            return this;
-        }
-
-        /**
-         * Adds an output tray.
-         * <p>
-         * <strong>Required:</strong> No
-         * </p>
-         *
-         * @param outputTray A tray.
-         * @param isDefault Whether this is the default.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If set as default and there
-         *     is already a default.
-         *
-         * @see PrintAttributes.Tray
-         */
-        public Builder addOutputTray(Tray outputTray, boolean isDefault) {
-            if (mPrototype.mOutputTrays == null) {
-                mPrototype.mOutputTrays = new ArrayList<Tray>();
-            }
-            final int insertionIndex = mPrototype.mOutputTrays.size();
-            mPrototype.mOutputTrays.add(outputTray);
-            if (isDefault) {
-                throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
-                mPrototype.mDefaults[PROPERTY_OUTPUT_TRAY] = insertionIndex;
-            }
-            return this;
-        }
-
-        /**
-         * Sets the color modes.
-         * <p>
-         * <strong>Required:</strong> Yes
-         * </p>
-         *
-         * @param colorModes The color mode bit mask.
-         * @param defaultColorMode The default color mode.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If color modes contains an invalid
-         *         mode bit or if the default color mode is invalid.
-         *
-         * @see PrintAttributes#COLOR_MODE_COLOR
-         * @see PrintAttributes#COLOR_MODE_MONOCHROME
-         */
-        public Builder setColorModes(int colorModes, int defaultColorMode) {
-            int currentModes = colorModes;
-            while (currentModes > 0) {
-                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
-                currentModes &= ~currentMode;
-                PrintAttributes.enforceValidColorMode(currentMode);
-            }
-            if ((colorModes & defaultColorMode) == 0) {
-                throw new IllegalArgumentException("Default color mode not in color modes.");
-            }
-            PrintAttributes.enforceValidColorMode(colorModes);
-            mPrototype.mColorModes = colorModes;
-            mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
-            return this;
-        }
-
-        /**
-         * Set the duplex modes.
-         * <p>
-         * <strong>Required:</strong> No
-         * </p>
-         *
-         * @param duplexModes The duplex mode bit mask.
-         * @param defaultDuplexMode The default duplex mode.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If duplex modes contains an invalid
-         *         mode bit or if the default duplex mode is invalid.
-         *
-         * @see PrintAttributes#DUPLEX_MODE_NONE
-         * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
-         * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
-         */
-        public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
-            int currentModes = duplexModes;
-            while (currentModes > 0) {
-                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
-                currentModes &= ~currentMode;
-                PrintAttributes.enforceValidDuplexMode(currentMode);
-            }
-            if ((duplexModes & defaultDuplexMode) == 0) {
-                throw new IllegalArgumentException("Default duplex mode not in duplex modes.");
-            }
-            PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
-            mPrototype.mDuplexModes = duplexModes;
-            mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
-            return this;
-        }
-
-        /**
-         * Sets the fitting modes.
-         * <p>
-         * <strong>Required:</strong> No
-         * </p>
-         *
-         * @param fittingModes The fitting mode bit mask.
-         * @param defaultFittingMode The default fitting mode.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If fitting modes contains an invalid
-         *         mode bit or if the default fitting mode is invalid.
-         *
-         * @see PrintAttributes#FITTING_MODE_NONE
-         * @see PrintAttributes#FITTING_MODE_FIT_TO_PAGE
-         */
-        public Builder setFittingModes(int fittingModes, int defaultFittingMode) {
-            int currentModes = fittingModes;
-            while (currentModes > 0) {
-                final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
-                currentModes &= ~currentMode;
-                PrintAttributes.enfoceValidFittingMode(currentMode);
-            }
-            if ((fittingModes & defaultFittingMode) == 0) {
-                throw new IllegalArgumentException("Default fitting mode not in fiting modes.");
-            }
-            PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
-            mPrototype.mFittingModes = fittingModes;
-            mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
-            return this;
-        }
-
-        /**
-         * Sets the orientations.
-         * <p>
-         * <strong>Required:</strong> Yes
-         * </p>
-         *
-         * @param orientations The orientation bit mask.
-         * @param defaultOrientation The default orientation.
-         * @return This builder.
-         *
-         * @throws IllegalArgumentException If orientations contains an invalid
-         *         mode bit or if the default orientation is invalid.
-         *
-         * @see PrintAttributes#ORIENTATION_PORTRAIT
-         * @see PrintAttributes#ORIENTATION_LANDSCAPE
-         */
-        public Builder setOrientations(int orientations, int defaultOrientation) {
-            int currentOrientaions = orientations;
-            while (currentOrientaions > 0) {
-                final int currentOrnt = (1 << Integer.numberOfTrailingZeros(currentOrientaions));
-                currentOrientaions &= ~currentOrnt;
-                PrintAttributes.enforceValidOrientation(currentOrnt);
-            }
-            if ((orientations & defaultOrientation) == 0) {
-                throw new IllegalArgumentException("Default orientation not in orientations.");
-            }
-            PrintAttributes.enforceValidOrientation(defaultOrientation);
-            mPrototype.mOrientations = orientations;
-            mPrototype.mDefaults[PROPERTY_ORIENTATION] = defaultOrientation;
+        public Builder setCapabilities(PrinterCapabilitiesInfo capabilities) {
+            mPrototype.mCapabilities = capabilities;
             return this;
         }
 
@@ -919,10 +277,8 @@
             return new PrinterInfo(mPrototype);
         }
 
-        private void throwIfDefaultAlreadySpecified(int propertyIndex) {
-            if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
-                throw new IllegalArgumentException("Default already specified.");
-            }
+        private boolean isValidStatus(int status) {
+            return (status == PrinterInfo.STATUS_READY);
         }
     }
 
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index e6fdbf9..16b7a26 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -10,16 +10,14 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
+ * See the License for the specific languagÿe governing permissions and
  * limitations under the License.
  */
 
 package android.printservice;
 
-import android.os.ICancellationSignal;
-import android.print.IPrinterDiscoveryObserver;
+import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintJobInfo;
-import android.print.PrinterId;
 import android.printservice.IPrintServiceClient;
 
 /**
@@ -29,9 +27,7 @@
  */
 oneway interface IPrintService {
     void setClient(IPrintServiceClient client);
-    void onRequestUpdatePrinters(in List<PrinterId> printerIds);
-    void onRequestCancelPrintJob(in PrintJobInfo printJobInfo);
+    void requestCancelPrintJob(in PrintJobInfo printJobInfo);
     void onPrintJobQueued(in PrintJobInfo printJobInfo);
-    void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
-    void onStopPrinterDiscovery();
+    void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
 }
diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java
index 2a1581a..7437dc5 100644
--- a/core/java/android/printservice/PrintDocument.java
+++ b/core/java/android/printservice/PrintDocument.java
@@ -55,14 +55,14 @@
     }
 
     /**
-     * Gets the data associated with this document. It is a responsibility of the
-     * client to open a stream to the returned file descriptor and fully read the
-     * data.
+     * Gets the data associated with this document.
      * <p>
-     * <strong>Note:</strong> It is your responsibility to close the file descriptor.
+     * <strong>Note: </strong> It is a responsibility of the client to open a
+     * stream to the returned file descriptor, fully read the data, and close
+     * the file descriptor.
      * </p>
      *
-     * @return A file descriptor for reading the data or <code>null</code>.
+     * @return A file descriptor for reading the data.
      */
     public FileDescriptor getData() {
         ParcelFileDescriptor source = null;
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 64c079e..5f9a730 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -21,9 +21,9 @@
 import android.util.Log;
 
 /**
- * This class represents a print job from the perspective of a
- * print service. It provides APIs for observing the print job
- * state and performing operations on the print job.
+ * This class represents a print job from the perspective of a print
+ * service. It provides APIs for observing the print job state and
+ * performing operations on the print job.
  */
 public final class PrintJob {
 
@@ -38,7 +38,8 @@
     PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) {
         mCachedInfo = jobInfo;
         mPrintServiceClient = client;
-        mDocument = new PrintDocument(mCachedInfo.getId(), client, jobInfo.getDocumentInfo());
+        mDocument = new PrintDocument(mCachedInfo.getId(), client,
+                jobInfo.getDocumentInfo());
     }
 
     /**
@@ -77,7 +78,7 @@
     }
 
     /**
-     * Gets the document of this print job.
+     * Gets the printed document.
      *
      * @return The document.
      */
@@ -87,11 +88,12 @@
 
     /**
      * Gets whether this print job is queued. Such a print job is
-     * ready to be printed and can be started.
+     * ready to be printed and can be started or cancelled.
      *
      * @return Whether the print job is queued.
      *
      * @see #start()
+     * @see #cancel()
      */
     public boolean isQueued() {
         return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
@@ -112,6 +114,42 @@
     }
 
     /**
+     * Gets whether this print job is completed. Such a print job
+     * is successfully printed. This is a final state.
+     *
+     * @return Whether the print job is completed.
+     *
+     * @see #complete()
+     */
+    public boolean isCompleted() {
+        return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
+    }
+
+    /**
+     * Gets whether this print job is failed. Such a print job is
+     * not successfully printed due to an error. This is a final state.
+     *
+     * @return Whether the print job is failed.
+     *
+     * @see #fail(CharSequence)
+     */
+    public boolean isFailed() {
+        return getInfo().getState() == PrintJobInfo.STATE_FAILED;
+    }
+
+    /**
+     * Gets whether this print job is cancelled. Such a print job was
+     * cancelled as a result of a user request. This is a final state.
+     *
+     * @return Whether the print job is cancelled.
+     *
+     * @see #cancel()
+     */
+    public boolean isCancelled() {
+        return getInfo().getState() == PrintJobInfo.STATE_FAILED;
+    }
+
+    /**
      * Starts the print job. You should call this method if {@link
      * #isQueued()} returns true and you started printing.
      *
@@ -163,12 +201,13 @@
     /**
      * 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)}.
+     * the print job as a response to a call to {@link
+     * PrintService#onRequestCancelPrintJob(PrintJob)}.
      *
-     * @return Whether the job as canceled.
+     * @return Whether the job is canceled.
      *
      * @see #isStarted()
+     * @see #isQueued()
      */
     public boolean cancel() {
         if (isQueued() || isStarted()) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 49384db..92bccd4 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -25,10 +25,9 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.print.IPrinterDiscoveryObserver;
+import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintJobInfo;
 import android.print.PrinterId;
-import android.print.PrinterInfo;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -37,84 +36,82 @@
 
 /**
  * <p>
- * This is the base class for implementing print services. A print service
- * knows how to discover and interact one or more printers via one or more
- * protocols.
+ * This is the base class for implementing print services. A print service knows
+ * how to discover and interact one or more printers via one or more protocols.
  * </p>
  * <h3>Printer discovery</h3>
  * <p>
- * A print service is responsible for discovering and reporting printers.
- * A printer discovery period starts with a call to
- * {@link #onStartPrinterDiscovery()} and ends with a call to
- * {@link #onStopPrinterDiscovery()}. During a printer discovery
- * period the print service reports newly discovered printers by
- * calling {@link #addDiscoveredPrinters(List)} and reports added printers
- * that disappeared by calling {@link #removeDiscoveredPrinters(List)}.
- * Calls to {@link #addDiscoveredPrinters(List)} and
- * {@link #removeDiscoveredPrinters(List)} before a call to
- * {@link #onStartPrinterDiscovery()} and after a call to
- * {@link #onStopPrinterDiscovery()} are a no-op.
+ * A print service is responsible for discovering printers, adding discovered printers,
+ * removing added printers, and updating added printers. When the system is interested
+ * in printers managed by your service it will call {@link
+ * #onCreatePrinterDiscoverySession()} from which you must return a new {@link
+ * PrinterDiscoverySession} instance. The returned session encapsulates the interaction
+ * between the system and your service during printer discovery. For description of this
+ * interaction refer to the documentation for {@link PrinterDiscoverySession}.
  * </p>
  * <p>
- * For every printer discovery period all printers have to be added. Each
- * printer known to this print service should be added only once during a
- * discovery period, unless it was added and then removed before that.
- * Only an already added printer can be removed.
+ * For every printer discovery session all printers have to be added since system does
+ * not retain printers across sessions. Hence, each printer known to this print service
+ * should be added only once during a discovery session. Only an already added printer
+ * can be removed or updated. Removed printers can be added again.
  * </p>
  * <h3>Print jobs</h3>
  * <p>
- * When a new print job targeted to the printers managed by this print
- * service is queued, i.e. ready for processing by the print service,
- * a call to {@link #onPrintJobQueued(PrintJob)} is made and the print
- * service may handle it immediately or schedule that for an appropriate
- * time in the future. The list of all print jobs for this service
- * are be available by calling {@link #getPrintJobs()}.
+ * When a new print job targeted to a printer managed by this print service is is queued,
+ * i.e. ready for processing by the print service, you will receive a call to {@link
+ * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately
+ * or schedule that for an appropriate time in the future. The list of all active print
+ * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active
+ * print jobs are ones that are queued or started.
  * </p>
  * <p>
- * A print service is responsible for setting the print job state as
- * appropriate while processing it. Initially, a print job is in a
- * {@link PrintJobInfo#STATE_QUEUED} state which means that the data to
- * be printed is spooled by the system and the print service can obtain
- * that data by calling {@link PrintJob#getDocument()}. A queued print
- * job's {@link PrintJob#isQueued()} method returns true.
+ * A print service is responsible for setting a print job's state as appropriate
+ * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued()
+ * PrintJob.isQueued()} returns true, which means that the document to be printed is
+ * spooled by the system and the print service can begin processing it. You can obtain
+ * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()}
+ * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}.
+ * After the print service starts printing the data it should set the print job's
+ * state to started by calling {@link PrintJob#start()} after which
+ * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful
+ * completion, the print job should be marked as completed by calling {@link
+ * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted()
+ * PrintJob.isCompleted()} would return true. In case of a failure, the print job should
+ * be marked as failed by calling {@link PrintJob#fail(CharSequence) PrintJob.fail(
+ * CharSequence)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
+ * return true.
  * </p>
  * <p>
- * After the print service starts printing the data it should set the
- * print job state to {@link PrintJobInfo#STATE_STARTED} by calling
- * {@link PrintJob#start()}. Upon successful completion, the print job
- * state has to be set to {@link PrintJobInfo#STATE_COMPLETED} by calling
- * {@link PrintJob#complete()}. In case of a failure, the print job
- * state should be set to {@link PrintJobInfo#STATE_FAILED} by calling
- * {@link PrintJob#fail(CharSequence)}. If a print job is in a
- * {@link PrintJobInfo#STATE_STARTED} state, i.e. {@link PrintJob#isStarted()}
- * return true, and the user requests to cancel it, the print service will
- * receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
- * requests from the service to do a best effort in canceling the job. In
- * case the job is successfully canceled, its state has to be set to
- * {@link PrintJobInfo#STATE_CANCELED}. by calling {@link PrintJob#cancel()}.
+ * If a print job is queued or started and the user requests to cancel it, the print
+ * service will receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
+ * requests from the service to do best effort in canceling the job. In case the job
+ * is successfully canceled, its state has to be marked as cancelled by calling {@link
+ * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled()
+ * PrintJob.isCacnelled()} would return true.
  * </p>
  * <h3>Lifecycle</h3>
  * <p>
- * The lifecycle of a print service is managed exclusively by the system
- * and follows the established service lifecycle. Additionally, starting
- * or stopping a print service is triggered exclusively by an explicit
- * user action through enabling or disabling it in the device settings.
- * After the system binds to a print service, it calls {@link #onConnected()}.
- * This method can be overriden by clients to perform post binding setup.
- * Also after the system unbinds from a print service, it calls
- * {@link #onDisconnected()}. This method can be overriden by clients to
- * perform post unbinding cleanup.
+ * The lifecycle of a print service is managed exclusively by the system and follows
+ * the established service lifecycle. Additionally, starting or stopping a print service
+ * is triggered exclusively by an explicit user action through enabling or disabling it
+ * in the device settings. After the system binds to a print service, it calls {@link
+ * #onConnected()}. This method can be overriden by clients to perform post binding setup.
+ * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}.
+ * This method can be overriden by clients to perform post unbinding cleanup. Your should
+ * not do any work after the system disconnected from your print service since the
+ * service can be killed at any time to reclaim memory. The system will not disconnect
+ * from a print service if there are active print jobs for the printers managed by it.
  * </p>
  * <h3>Declaration</h3>
  * <p>
- * A print service is declared as any other service in an AndroidManifest.xml
- * but it must also specify that it handles the {@link android.content.Intent}
- * with action {@link #SERVICE_INTERFACE}. Failure to declare this intent
- * will cause the system to ignore the print service. Additionally, a print
- * service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE}
- * permission to ensure that only the system can bind to it. Failure to
- * declare this intent will cause the system to ignore the print service.
- * Following is an example declaration:
+ * A print service is declared as any other service in an AndroidManifest.xml but it must
+ * also specify that it handles the {@link android.content.Intent} with action {@link
+ * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent
+ * will cause the system to ignore the print service. Additionally, a print service must
+ * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE
+ * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can
+ * bind to it. Failure to declare this intent will cause the system to ignore the print
+ * service. Following is an example declaration:
  * </p>
  * <pre>
  * &lt;service android:name=".MyPrintService"
@@ -127,17 +124,15 @@
  * </pre>
  * <h3>Configuration</h3>
  * <p>
- * A print service can be configured by specifying an optional settings
- * activity which exposes service specific options, an optional add
- * prints activity which is used for manual addition of printers, vendor
- * name ,etc. It is a responsibility of the system to launch the settings
- * and add printers activities when appropriate.
+ * A print service can be configured by specifying an optional settings activity which
+ * exposes service specific settings, an optional add printers activity which is used for
+ * manual addition of printers, vendor name ,etc. It is a responsibility of the system
+ * to launch the settings and add printers activities when appropriate.
  * </p>
  * <p>
- * A print service is configured by providing a
- * {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
- * the service. A service declaration with a meta-data tag is presented
- * below:
+ * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data}
+ * entry in the manifest when declaring the service. A service declaration with a meta-data
+ * tag is presented below:
  * <pre> &lt;service android:name=".MyPrintService"
  *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
  *     &lt;intent-filter&gt;
@@ -147,8 +142,9 @@
  * &lt;/service&gt;</pre>
  * </p>
  * <p>
- * For more details refer to {@link #SERVICE_META_DATA} and
- * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>.
+ * For more details for how to configure your print service via the meta-data refer to
+ * {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService
+ * print-service}&gt;</code>.
  * </p>
  */
 public abstract class PrintService extends Service {
@@ -157,21 +153,25 @@
 
     /**
      * The {@link Intent} action that must be declared as handled by a service
-     * in its manifest to allow the system to recognize it as a print service.
+     * in its manifest for the system to recognize it as a print service.
      */
     public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
 
     /**
-     * Name under which a PrintService component publishes additional information
-     * about itself. This meta-data must reference an XML resource containing a
-     * <code>&lt;{@link android.R.styleable#PrintService print-service}&gt;</code>
-     * tag. This is a a sample XML file configuring a print service:
+     * Name under which a {@link PrintService} component publishes additional information
+     * about itself. This meta-data must reference a XML resource containing a <code>
+     * &lt;{@link android.R.styleable#PrintService print-service}&gt;</code> tag. This is
+     * a sample XML file configuring a print service:
      * <pre> &lt;print-service
      *     android:vendor="SomeVendor"
      *     android:settingsActivity="foo.bar.MySettingsActivity"
      *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
      *     . . .
      * /&gt;</pre>
+     * <p>
+     * For detailed configuration options that can be specified via the meta-data
+     * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}.
+     * </p>
      */
     public static final String SERVICE_META_DATA = "android.printservice";
 
@@ -181,12 +181,12 @@
 
     private IPrintServiceClient mClient;
 
-    private IPrinterDiscoveryObserver mDiscoveryObserver;
+    private int mLastSessionId = -1;
 
     @Override
-    protected void attachBaseContext(Context base) {
+    protected final void attachBaseContext(Context base) {
         super.attachBaseContext(base);
-        mHandler = new MyHandler(base.getMainLooper());
+        mHandler = new ServiceHandler(base.getMainLooper());
     }
 
     /**
@@ -204,203 +204,47 @@
     }
 
     /**
-     * Callback requesting from this service to start printer discovery.
-     * At the end of the printer discovery period the system will call
-     * {@link #onStopPrinterDiscovery()}. Discovered printers should be
-     * reported by calling {@link #addDiscoveredPrinters(List)} and reported
-     * ones that disappear should be reported by calling
-     * {@link #removeDiscoveredPrinters(List)}.
+     * Callback asking you to create a new {@link PrinterDiscoverySession}.
      *
-     * @see #onStopPrinterDiscovery()
-     * @see #addDiscoveredPrinters(List)
-     * @see #removeDiscoveredPrinters(List)
-     * @see #updateDiscoveredPrinters(List)
+     * @see PrinterDiscoverySession
      */
-    protected abstract void onStartPrinterDiscovery();
+    protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
 
     /**
-     * Callback requesting from this service to stop printer discovery.
-     *
-     * @see #onStartPrinterDiscovery()
-     * @see #addDiscoveredPrinters(List)
-     * @see #removeDiscoveredPrinters(List)
-     * @see #updateDiscoveredPrinters(List)
-     */
-    protected abstract void onStopPrinterDiscovery();
-
-    /**
-     * Adds discovered printers. This method should be called during a
-     * printer discovery period, i.e. after a call to
-     * {@link #onStartPrinterDiscovery()} and before the corresponding
-     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
-     * <p>
-     * <strong>Note:</strong> For every printer discovery period all
-     * printers have to be added. You can call this method as many times as
-     * necessary during the discovery period but should not pass in already
-     * added printers. If a printer is already added in the same printer
-     * discovery period, it will be ignored.
-     * </p>
-     * <p>
-     * A {@link PrinterInfo} can have all of its required attributes specified,
-     * or not. Whether all attributes are specified can be verified by calling
-     * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
-     * regardless if all required attributes are specified. When the system
-     * (and the user) needs to interact with a printer, you will receive a
-     * call to {@link #onRequestUpdatePrinters(List)}. If you fail to update
-     * a printer that was added without all required attributes via calling
-     * {@link #updateDiscoveredPrinters(List)}, then this printer will be
-     * ignored, i.e. considered unavailable.
-     * <p>
-     *
-     * @param printers A list with discovered printers.
-     *
-     * @see #updateDiscoveredPrinters(List)
-     * @see #removeDiscoveredPrinters(List)
-     * @see #onStartPrinterDiscovery()
-     * @see #onStopPrinterDiscovery()
-     */
-    public final void addDiscoveredPrinters(List<PrinterInfo> printers) {
-        final IPrinterDiscoveryObserver observer;
-        synchronized (mLock) {
-            observer = mDiscoveryObserver;
-        }
-        if (observer != null) {
-            try {
-                observer.onPrintersAdded(printers);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error adding discovered printers", re);
-            }
-        }
-    }
-
-    /**
-     * Removes discovered printers given their ids. This method should be called
-     * during a printer discovery period, i.e. after a call to
-     * {@link #onStartPrinterDiscovery()} and before the corresponding
-     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
-     * <p>
-     * For every printer discovery period all printers have to be added. You
-     * should remove only printers that were added in this printer discovery
-     * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
-     * this method as many times as necessary during the discovery period
-     * but should not pass in already removed printer ids. If a printer with
-     * a given id is already removed, it will be ignored.
-     * </p>
-     *
-     * @param printerIds A list with disappeared printer ids.
-     *
-     * @see #addDiscoveredPrinters(List)
-     * @see #updateDiscoveredPrinters(List)
-     * @see #onStartPrinterDiscovery()
-     * @see #onStopPrinterDiscovery()
-     */
-    public final void removeDiscoveredPrinters(List<PrinterId> printerIds) {
-        final IPrinterDiscoveryObserver observer;
-        synchronized (mLock) {
-            observer = mDiscoveryObserver;
-        }
-        if (observer != null) {
-            try {
-                observer.onPrintersRemoved(printerIds);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error removing discovered printers", re);
-            }
-        }
-    }
-
-    /**
-     * Updates discovered printers that are already added. This method should
-     * be called during a printer discovery period, i.e. after a call to
-     * {@link #onStartPrinterDiscovery()} and before the corresponding
-     * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
-     * <p>
-     * For every printer discovery period all printers have to be added. You
-     * should update only printers that were added in this printer discovery
-     * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
-     * this method as many times as necessary during the discovery period
-     * but should not try to update already removed or never added printers.
-     * If a printer is already removed or never added, it will be ignored.
-     * </p>
-     *
-     * @param printers A list with updated printers.
-     *
-     * @see #addDiscoveredPrinters(List)
-     * @see #removeDiscoveredPrinters(List)
-     * @see #onStartPrinterDiscovery()
-     * @see #onStopPrinterDiscovery()
-     */
-    public final void updateDiscoveredPrinters(List<PrinterInfo> printers) {
-        final IPrinterDiscoveryObserver observer;
-        synchronized (mLock) {
-            observer = mDiscoveryObserver;
-        }
-        if (observer != null) {
-            try {
-                observer.onPrintersUpdated(printers);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error updating discovered printers", re);
-            }
-        }
-    }
-
-    /**
-     * Called when the system will start interacting with a printer
-     * giving you a change to update it in case some of its capabilities
-     * have changed. For example, this method will be called when the
-     * user selects a printer. Hence, it updating this printer should
-     * be done as quickly as possible in order to achieve maximally
-     * smooth user experience.
-     * <p>
-     * A {@link PrinterInfo} can have all of its required attributes specified,
-     * or not. Whether all attributes are specified can be verified by calling
-     * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
-     * regardless if all required attributes are specified. When the system
-     * (and the user) needs to interact with a printer, you will receive a
-     * call to this method. If you fail to update a printer that was added
-     * without all required attributes via calling
-     * {@link #updateDiscoveredPrinters(List)}, then this printer will be
-     * ignored, i.e. considered unavailable.
-     * </p>
-     *
-     * @param printerIds The printers to be updated.
-     */
-    protected void onRequestUpdatePrinters(List<PrinterId> printerIds) {
-    }
-
-    /**
-     * Called when canceling of a print job is requested. The service
+     * Called when cancellation of a print job is requested. The service
      * should do best effort to fulfill the request. After the cancellation
-     * is performed, the print job should be set to a cancelled state by
+     * is performed, the print job should be marked as cancelled state by
      * calling {@link PrintJob#cancel()}.
      *
-     * @param printJob The print job to be canceled.
+     * @param printJob The print job to cancel.
+     *
+     * @see PrintJob#cancel() PrintJob.cancel()
+     * @see PrintJob#isCancelled() PrintJob.isCancelled()
      */
-    protected void onRequestCancelPrintJob(PrintJob printJob) {
-    }
+    protected abstract void onRequestCancelPrintJob(PrintJob printJob);
 
     /**
      * Called when there is a queued print job for one of the printers
-     * managed by this print service. A queued print job is ready for
-     * processing by a print service which can get the data to be printed
-     * by calling {@link PrintJob#getDocument()}. This service may start
-     * processing the passed in print job or schedule handling of queued
-     * print jobs at a convenient time. The service can get the print
-     * jobs by a call to {@link #getPrintJobs()} and examine their state
-     * to find the ones with state {@link PrintJobInfo#STATE_QUEUED} by
-     * calling {@link PrintJob#isQueued()}.
+     * managed by this print service.
      *
      * @param printJob The new queued print job.
      *
-     * @see #getPrintJobs()
+     * @see PrintJob#isQueued() PrintJob.isQueued()
+     * @see #getActivePrintJobs()
      */
     protected abstract void onPrintJobQueued(PrintJob printJob);
 
     /**
-     * Gets the print jobs for the printers managed by this service.
+     * Gets the active print jobs for the printers managed by this service.
+     * Active print jobs are ones that are not in a final state, i.e. whose
+     * state is queued or started.
      *
-     * @return The print jobs.
+     * @return The active print jobs.
+     *
+     * @see PrintJob#isQueued() PrintJob.isQueued()
+     * @see PrintJob#isStarted() PrintJob.isStarted()
      */
-    public final List<PrintJob> getPrintJobs() {
+    public final List<PrintJob> getActivePrintJobs() {
         final IPrintServiceClient client;
         synchronized (mLock) {
             client = mClient;
@@ -428,14 +272,14 @@
     }
 
     /**
-     * Generates a global printer id given the printer's locally unique name.
+     * Generates a global printer id given the printer's locally unique one.
      *
-     * @param printerName The printer name.
+     * @param localId A locally unique id in the context of your print service.
      * @return Global printer id.
      */
-    public final PrinterId generatePrinterId(String printerName) {
+    public final PrinterId generatePrinterId(String localId) {
         return new PrinterId(new ComponentName(getPackageName(),
-                getClass().getName()), printerName);
+                getClass().getName()), localId);
     }
 
     @Override
@@ -443,69 +287,58 @@
         return new IPrintService.Stub() {
             @Override
             public void setClient(IPrintServiceClient client) {
-                mHandler.obtainMessage(MyHandler.MSG_SET_CLEINT, client).sendToTarget();
+                mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
+                        .sendToTarget();
             }
 
             @Override
-            public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
+            public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+                mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION,
                         observer).sendToTarget();
             }
 
             @Override
-            public void onStopPrinterDiscovery() {
-                mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY);
-            }
-
-            @Override
-            public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
-                        printerIds).sendToTarget();
-            }
-
-            @Override
-            public void onRequestCancelPrintJob(PrintJobInfo printJobInfo) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
+            public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
+                mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
                         printJobInfo).sendToTarget();
             }
 
             @Override
             public void onPrintJobQueued(PrintJobInfo printJobInfo) {
-                mHandler.obtainMessage(MyHandler.MSG_ON_PRINTJOB_QUEUED,
+                mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED,
                         printJobInfo).sendToTarget();
             }
         };
     }
 
-    private final class MyHandler extends Handler {
-        public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
-        public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
+    private final class ServiceHandler extends Handler {
+        public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+        public static final int MSG_ON_PRINTJOB_QUEUED = 2;
         public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
-        public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 4;
-        public static final int MSG_ON_PRINTJOB_QUEUED = 5;
-        public static final int MSG_SET_CLEINT = 6;
+        public static final int MSG_SET_CLEINT = 4;
 
-        public MyHandler(Looper looper) {
+        public ServiceHandler(Looper looper) {
             super(looper, null, true);
         }
 
         @Override
-        @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
             final int action = message.what;
             switch (action) {
-                case MSG_ON_START_PRINTER_DISCOVERY: {
-                    synchronized (mLock) {
-                        mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj;
+                case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: {
+                    IPrinterDiscoverySessionObserver observer =
+                            (IPrinterDiscoverySessionObserver) message.obj;
+                    PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
+                    if (session == null) {
+                        throw new NullPointerException("session cannot be null");
                     }
-                    onStartPrinterDiscovery();
-                } break;
-
-                case MSG_ON_STOP_PRINTER_DISCOVERY: {
                     synchronized (mLock) {
-                        mDiscoveryObserver = null;
+                        if (session.getId() == mLastSessionId) {
+                            throw new IllegalStateException("cannot reuse sessions");
+                        }
+                        mLastSessionId = session.getId();
                     }
-                    onStopPrinterDiscovery();
+                    session.setObserver(observer);
                 } break;
 
                 case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
@@ -513,11 +346,6 @@
                     onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
                 } break;
 
-                case MSG_ON_REQUEST_UPDATE_PRINTERS: {
-                    List<PrinterId> printerIds = (List<PrinterId>) message.obj;
-                    onRequestUpdatePrinters(printerIds);
-                } break;
-
                 case MSG_ON_PRINTJOB_QUEUED: {
                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
                     onPrintJobQueued(new PrintJob(printJobInfo, mClient));
@@ -527,9 +355,6 @@
                     IPrintServiceClient client = (IPrintServiceClient) message.obj;
                     synchronized (mLock) {
                         mClient = client;
-                        if (client == null) {
-                            mDiscoveryObserver = null;
-                        }
                     }
                     if (client != null) {
                         onConnected();
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
new file mode 100644
index 0000000..5bc0f2e
--- /dev/null
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.printservice;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.print.IPrinterDiscoverySessionController;
+import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * This class encapsulates the interaction between a print service and the
+ * system during printer discovery. During printer discovery you are responsible
+ * for adding discovered printers, removing already added printers that
+ * disappeared, and updating already added printers.
+ * <p>
+ * The opening of the session is announced by a call to {@link
+ * PrinterDiscoverySession#onOpen(List)} at which point you should start printer
+ * discovery. The closing of the session is announced by a call to {@link
+ * PrinterDiscoverySession#onClose()} at which point you should stop printer
+ * discovery. Discovered printers are added by invoking {@link
+ * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared
+ * are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}.
+ * Added printers whose properties or capabilities changed are updated through
+ * a call to {@link PrinterDiscoverySession#updatePrinters(List)}.
+ * </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
+ * 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 require 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.
+ * </p>
+ * <p>
+ * During printer discovery all printers that are known to your print service
+ * have to be added. The system does not retain any printers from previous
+ * sessions.
+ * </p>
+ */
+public abstract class PrinterDiscoverySession {
+    private static final String LOG_TAG = "PrinterDiscoverySession";
+
+    private static int sIdCounter = 0;
+
+    private final Object mLock = new Object();
+
+    private final Handler mHandler;
+
+    private final int mId;
+
+    private IPrinterDiscoverySessionController mController;
+
+    private IPrinterDiscoverySessionObserver mObserver;
+
+    /**
+     * Constructor.
+     *
+     * @param context A context instance.
+     */
+    public PrinterDiscoverySession(Context context) {
+        mId = sIdCounter++;
+        mHandler = new SessionHandler(context.getMainLooper());
+        mController = new PrinterDiscoverySessionController(this);
+    }
+
+    void setObserver(IPrinterDiscoverySessionObserver observer) {
+        synchronized (mLock) {
+            mObserver = observer;
+            try {
+                mObserver.setController(mController);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error setting session controller", re);
+            }
+        }
+    }
+
+    int getId() {
+        return mId;
+    }
+
+    /**
+     * Adds discovered printers. Adding an already added printer has no effect.
+     * Removed printers can be added again. You can call this method multiple
+     * times during printer discovery.
+     * <p>
+     * <strong>Note: </strong> Calling this method when the session is closed,
+     * which is if {@link #isClosed()} returns true, will throw an {@link
+     * IllegalStateException}.
+     * </p>
+     *
+     * @param printers The printers to add.
+     *
+     * @see #removePrinters(List)
+     * @see #updatePrinters(List)
+     * @see #isClosed()
+     */
+    public final void addPrinters(List<PrinterInfo> printers) {
+        final IPrinterDiscoverySessionObserver observer;
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            observer = mObserver;
+        }
+        if (observer != null) {
+            try {
+                observer.onPrintersAdded(printers);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error adding printers", re);
+            }
+        }
+    }
+
+    /**
+     * Removes added printers. Removing an already removed or never added
+     * printer has no effect. Removed printers can be added again. You
+     * can call this method multiple times during printer discovery.
+     * <p>
+     * <strong>Note: </strong> Calling this method when the session is closed,
+     * which is if {@link #isClosed()} returns true, will throw an {@link
+     * IllegalStateException}.
+     * </p>
+     *
+     * @param printerIds The ids of the removed printers.
+     *
+     * @see #addPrinters(List)
+     * @see #updatePrinters(List)
+     * @see #isClosed()
+     */
+    public final void removePrinters(List<PrinterId> printerIds) {
+        final IPrinterDiscoverySessionObserver observer;
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            observer = mObserver;
+        }
+        if (observer != null) {
+            try {
+                observer.onPrintersRemoved(printerIds);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error removing printers", re);
+            }
+        }
+    }
+
+    /**
+     * Updates added printers. Updating a printer that was not added or that
+     * was removed has no effect. You can call this method multiple times
+     * during printer discovery.
+     * <p>
+     * <strong>Note: </strong> Calling this method when the session is closed,
+     * which is if {@link #isClosed()} returns true, will throw an {@link
+     * IllegalStateException}.
+     * </p>
+     *
+     * @param printers The printers to update.
+     *
+     * @see #addPrinters(List)
+     * @see #removePrinters(List)
+     * @see #isClosed()
+     */
+    public final void updatePrinters(List<PrinterInfo> printers) {
+        final IPrinterDiscoverySessionObserver observer;
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            observer = mObserver;
+        }
+        if (observer != null) {
+            try {
+                observer.onPrintersUpdated(printers);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error updating printers", re);
+            }
+        }
+    }
+
+    /**
+     * Callback notifying you that the session is open and you should start
+     * printer discovery. Discovered printers should be added via calling
+     * {@link #addPrinters(List)}. Added printers that disappeared should be
+     * removed via calling {@link #removePrinters(List)}. Added printers whose
+     * properties or capabilities changes should be updated via calling {@link
+     * #updatePrinters(List)}. When the session is closed you will receive a
+     * call to {@link #onClose()}.
+     * <p>
+     * During printer discovery all printers that are known to your print
+     * service have to be added. The system does not retain any printers from
+     * previous sessions.
+     * </p>
+     * <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.
+     * </p>
+     *
+     * @see #onClose()
+     * @see #isClosed()
+     * @see #addPrinters(List)
+     * @see #removePrinters(List)
+     * @see #updatePrinters(List)
+     */
+    public abstract void onOpen(List<PrinterId> priorityList);
+
+    /**
+     * Callback notifying you that the session is closed and you should stop
+     * printer discovery. After the session is closed and any attempt to call
+     * any of its methods will throw an exception. Whether a session is closed
+     * can be checked by calling {@link #isClosed()}. Once the session is closed
+     * it will never be opened again.
+     *
+     * @see #onOpen(List)
+     * @see #isClosed()
+     * @see #addPrinters(List)
+     * @see #removePrinters(List)
+     * @see #updatePrinters(List)
+     */
+    public abstract void onClose();
+
+    /**
+     * Requests that you update a printer. You are responsible for updating
+     * the printer by also reporting its capabilities 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.
+     * However, after this method is called you are expected to update the
+     * 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>
+     *
+     * @param printerId The printer id.
+     *
+     * @see #updatePrinters(List)
+     * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
+     *      PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
+     */
+    public abstract void onRequestPrinterUpdate(PrinterId printerId);
+
+    /**
+     * Gets whether this session is closed.
+     *
+     * @return Whether the session is closed.
+     */
+    public final boolean isClosed() {
+        synchronized (mLock) {
+            return (mController == null && mObserver == null);
+        }
+    }
+
+    void close() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            mController = null;
+            mObserver = null;
+        }
+    }
+
+    private void throwIfClosedLocked() {
+        if (isClosed()) {
+            throw new IllegalStateException("Session is closed");
+        }
+    }
+
+    private final class SessionHandler extends Handler {
+        public static final int MSG_OPEN = 1;
+        public static final int MSG_CLOSE = 2;
+        public static final int MSG_REQUEST_PRINTER_UPDATE = 3;
+
+        public SessionHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_OPEN: {
+                    List<PrinterId> priorityList = (List<PrinterId>) message.obj;
+                    onOpen(priorityList);
+                } break;
+
+                case MSG_CLOSE: {
+                    onClose();
+                    close();
+                } break;
+
+                case MSG_REQUEST_PRINTER_UPDATE: {
+                    PrinterId printerId = (PrinterId) message.obj;
+                    onRequestPrinterUpdate(printerId);
+                } break;
+            }
+        }
+    }
+
+    private static final class PrinterDiscoverySessionController extends
+            IPrinterDiscoverySessionController.Stub {
+        private final WeakReference<PrinterDiscoverySession> mWeakSession;
+
+        public PrinterDiscoverySessionController(PrinterDiscoverySession session) {
+            mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
+        }
+
+        @Override
+        public void open(List<PrinterId> priorityList) {
+            PrinterDiscoverySession session = mWeakSession.get();
+            if (session != null) {
+                session.mHandler.obtainMessage(SessionHandler.MSG_OPEN,
+                        priorityList).sendToTarget();
+            }
+        }
+
+        @Override
+        public void close() {
+            PrinterDiscoverySession session = mWeakSession.get();
+            if (session != null) {
+                session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE);
+            }
+        }
+
+        @Override
+        public void requestPrinterUpdate(PrinterId printerId) {
+            PrinterDiscoverySession session = mWeakSession.get();
+            if (session != null) {
+                session.mHandler.obtainMessage(
+                        SessionHandler.MSG_REQUEST_PRINTER_UPDATE,
+                        printerId).sendToTarget();
+            }
+        }
+    };
+}
diff --git a/core/java/android/printservice/package.html b/core/java/android/printservice/package.html
new file mode 100644
index 0000000..6b0327c
--- /dev/null
+++ b/core/java/android/printservice/package.html
@@ -0,0 +1,24 @@
+<HTML>
+<BODY>
+<p>
+Provides classes for implementing print services. Print services are plug-in components
+that know how to talk to printers via some standard protocols. These services serve as a
+bridge between the system and the printers. Hence, the printer and print protocol specific
+implementation is factored out of the system and can by independently developed and updated.
+</p>
+<p>
+A print service implementation should extend {@link android.printservice.PrintService}
+and implement its abstract methods. Also the print service has to follow the contract for
+managing print {@link android.printservice.PrintJob}s to ensure correct interaction with
+the system and consistent user experience.
+<p/>
+<p>
+The system is responsible for starting and stopping a print service depending on whether
+there are active print jobs for the printers managed by the service. The print service
+should also perform printer discovery in a timely fashion to ensure good user experience.
+The interaction between the system and the print service during printer discovery is
+encapsulated by a {@link android.printservice.PrinterDiscoverySession} instance created
+by the print service when requested by the system.
+</p>
+</BODY>
+</HTML>