Merge "Implemented advanced printer selection and API refactoring." into klp-dev
diff --git a/Android.mk b/Android.mk
index 8e59ffa..3c9cc2c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -162,8 +162,6 @@
 	core/java/android/os/IVibratorService.aidl \
 	core/java/android/service/notification/INotificationListener.aidl \
 	core/java/android/print/ILayoutResultCallback.aidl \
-	core/java/android/print/IPrinterDiscoverySessionController.aidl \
-	core/java/android/print/IPrinterDiscoverySessionObserver.aidl \
 	core/java/android/print/IPrintDocumentAdapter.aidl \
 	core/java/android/print/IPrintClient.aidl \
 	core/java/android/print/IPrintManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 3fe11a6f..394acba 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19038,7 +19038,7 @@
     method public android.print.PrintAttributes getAttributes();
     method public int getCopies();
     method public int getId();
-    method public java.lang.CharSequence getLabel();
+    method public java.lang.String getLabel();
     method public android.print.PageRange[] getPages();
     method public android.print.PrinterId getPrinterId();
     method public int getState();
@@ -19162,7 +19162,7 @@
   public final class PrintJob {
     method public boolean cancel();
     method public boolean complete();
-    method public boolean fail(java.lang.CharSequence);
+    method public boolean fail(java.lang.String);
     method public android.printservice.PrintDocument getDocument();
     method public int getId();
     method public android.print.PrintJobInfo getInfo();
@@ -19191,11 +19191,15 @@
   }
 
   public abstract class PrinterDiscoverySession {
-    ctor public PrinterDiscoverySession(android.content.Context);
+    ctor public PrinterDiscoverySession();
     method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
-    method public abstract void onClose();
-    method public abstract void onOpen(java.util.List<android.print.PrinterId>);
+    method public final java.util.List<android.print.PrinterInfo> getPrinters();
+    method public final boolean isDestroyed();
+    method public final boolean isPrinterDiscoveryStarted();
+    method public abstract void onDestroy();
     method public abstract void onRequestPrinterUpdate(android.print.PrinterId);
+    method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
+    method public abstract void onStopPrinterDiscovery();
     method public final void removePrinters(java.util.List<android.print.PrinterId>);
     method public final void updatePrinters(java.util.List<android.print.PrinterInfo>);
   }
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 8178180..5c8a22a 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.os.ParcelFileDescriptor;
+import android.print.PrinterId;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintClient;
 import android.print.IPrintSpoolerClient;
@@ -40,10 +41,15 @@
     void createPrintJob(String printJobName, in IPrintClient client,
             in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
             IPrintSpoolerCallbacks callback, int appId, int sequence);
-    void setPrintJobState(int printJobId, int status, CharSequence error,
+    void setPrintJobState(int printJobId, int status, String error,
             IPrintSpoolerCallbacks callback, int sequence);
     void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
             int sequence);
     void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
     void setClient(IPrintSpoolerClient client);
+
+    // Printer discovery APIs
+    void onPrintersAdded(in List<PrinterInfo> printers);
+    void onPrintersRemoved(in List<PrinterId> printerIds);
+    void onPrintersUpdated(in List<PrinterInfo> printerIds);
 }
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 8db2169..da60120 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -17,7 +17,6 @@
 package android.print;
 
 import android.content.ComponentName;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrinterId;
 import android.print.PrintJobInfo;
 
@@ -28,8 +27,14 @@
  * @hide
  */
 oneway interface IPrintSpoolerClient {
-    void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
     void onPrintJobQueued(in PrintJobInfo printJob);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
+
+    // Printer discovery APIs
+    void createPrinterDiscoverySession();
+    void startPrinterDiscovery(in List<PrinterId> priorityList);
+    void stopPrinterDiscovery();
+    void requestPrinterUpdate(in PrinterId printerId);
+    void destroyPrinterDiscoverySession();
 }
diff --git a/core/java/android/print/IPrinterDiscoverySessionController.aidl b/core/java/android/print/IPrinterDiscoverySessionController.aidl
deleted file mode 100644
index 13116ef..0000000
--- a/core/java/android/print/IPrinterDiscoverySessionController.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.print.PrinterId;
-
-/**
-* Interface for the controlling part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionController {
-    void open(in List<PrinterId> priorityList);
-    void requestPrinterUpdate(in PrinterId printerId);
-    void close();
-}
diff --git a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl b/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
deleted file mode 100644
index a78924c..0000000
--- a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.print.IPrinterDiscoverySessionController;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for the observing part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionObserver {
-    void setController(IPrinterDiscoverySessionController controller);
-    void onPrintersAdded(in List<PrinterInfo> printers);
-    void onPrintersRemoved(in List<PrinterId> printerIds);
-    void onPrintersUpdated(in List<PrinterInfo> printerIds);
-}
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 2fb4751..602f3c1 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -104,7 +104,7 @@
     private int mId;
 
     /** The human readable print job label. */
-    private CharSequence mLabel;
+    private String mLabel;
 
     /** The unique id of the printer. */
     private PrinterId mPrinterId;
@@ -128,7 +128,7 @@
     private int mCopies;
 
     /** Failure reason if this job failed. */
-    private CharSequence mFailureReason;
+    private String mFailureReason;
 
     /** The pages to print */
     private PageRange[] mPageRanges;
@@ -163,7 +163,7 @@
 
     private PrintJobInfo(Parcel parcel) {
         mId = parcel.readInt();
-        mLabel = parcel.readCharSequence();
+        mLabel = parcel.readString();
         mPrinterId = parcel.readParcelable(null);
         mPrinterName = parcel.readString();
         mState = parcel.readInt();
@@ -171,9 +171,7 @@
         mUserId = parcel.readInt();
         mTag = parcel.readString();
         mCopies = parcel.readInt();
-        if (parcel.readInt() == 1) {
-            mFailureReason = parcel.readCharSequence();
-        }
+        mFailureReason = parcel.readString();
         if (parcel.readInt() == 1) {
             Parcelable[] parcelables = parcel.readParcelableArray(null);
             mPageRanges = new PageRange[parcelables.length];
@@ -214,7 +212,7 @@
      *
      * @return The label.
      */
-    public CharSequence getLabel() {
+    public String getLabel() {
         return mLabel;
     }
 
@@ -225,7 +223,7 @@
      *
      * @hide
      */
-    public void setLabel(CharSequence label) {
+    public void setLabel(String label) {
         mLabel = label;
     }
 
@@ -385,7 +383,7 @@
      *
      * @hide
      */
-    public CharSequence getFailureReason() {
+    public String getFailureReason() {
         return mFailureReason;
     }
 
@@ -396,7 +394,7 @@
      *
      * @hide
      */
-    public void setFailureReason(CharSequence failureReason) {
+    public void setFailureReason(String failureReason) {
         mFailureReason = failureReason;
     }
 
@@ -470,7 +468,7 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mId);
-        parcel.writeCharSequence(mLabel);
+        parcel.writeString(mLabel);
         parcel.writeParcelable(mPrinterId, flags);
         parcel.writeString(mPrinterName);
         parcel.writeInt(mState);
@@ -478,12 +476,7 @@
         parcel.writeInt(mUserId);
         parcel.writeString(mTag);
         parcel.writeInt(mCopies);
-        if (mFailureReason != null) {
-            parcel.writeInt(1);
-            parcel.writeCharSequence(mFailureReason);
-        } else {
-            parcel.writeInt(0);
-        }
+        parcel.writeString(mFailureReason);
         if (mPageRanges != null) {
             parcel.writeInt(1);
             parcel.writeParcelableArray(mPageRanges, flags);
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 636b9d4..531dcb2 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -374,14 +374,14 @@
 
             @Override
             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+                if (info == null) {
+                    throw new NullPointerException("document info cannot be null");
+                }
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
                     callback = mCallback;
                     clearLocked();
                 }
-                if (info == null) {
-                    throw new IllegalArgumentException("info cannot be null");
-                }
                 if (callback != null) {
                     try {
                         callback.onLayoutFinished(info, changed, mSequence);
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index ac782a8..6f567a6 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -229,10 +229,11 @@
         /**
          * Constructor.
          *
-         * @param prototype Prototype from which to start building.
+         * @param other Other info from which to start building.
          */
-        public Builder(PrinterInfo prototype) {
-            mPrototype = prototype;
+        public Builder(PrinterInfo other) {
+            mPrototype = new PrinterInfo();
+            mPrototype.copyFrom(other);
         }
 
         /**
diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java
index cfeb975..dbd7dd1 100644
--- a/core/java/android/print/pdf/PdfDocument.java
+++ b/core/java/android/print/pdf/PdfDocument.java
@@ -324,7 +324,7 @@
             /**
              * Creates a new builder with the mandatory page info attributes.
              *
-             * @param pageSize The page size in pixels.
+             * @param pageSize The page size in points, <strong>not</strong> dips.
              * @param pageNumber The page number.
              * @param density The page density in DPI.
              */
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index 16b7a26..2cee1d8 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -16,7 +16,7 @@
 
 package android.printservice;
 
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterId;
 import android.print.PrintJobInfo;
 import android.printservice.IPrintServiceClient;
 
@@ -29,5 +29,10 @@
     void setClient(IPrintServiceClient client);
     void requestCancelPrintJob(in PrintJobInfo printJobInfo);
     void onPrintJobQueued(in PrintJobInfo printJobInfo);
-    void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+
+    void createPrinterDiscoverySession();
+    void startPrinterDiscovery(in List<PrinterId> priorityList);
+    void stopPrinterDiscovery();
+    void requestPrinterUpdate(in PrinterId printerId);
+    void destroyPrinterDiscoverySession();
 }
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index f00b37c4..1e33fc0 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -29,7 +29,11 @@
 interface IPrintServiceClient {
     List<PrintJobInfo> getPrintJobInfos();
     PrintJobInfo getPrintJobInfo(int printJobId);
-    boolean setPrintJobState(int printJobId, int state, CharSequence error);
+    boolean setPrintJobState(int printJobId, int state, String error);
     boolean setPrintJobTag(int printJobId, String tag);
     oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
+
+    void onPrintersAdded(in List<PrinterInfo> printers);
+    void onPrintersRemoved(in List<PrinterId> printerIds);
+    void onPrintersUpdated(in List<PrinterInfo> printers);
 }
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 5f9a730..d2fbef2 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -24,6 +24,10 @@
  * 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.
+ * <p>
+ * <strong>Note: </strong> All methods of this class must be executed on the main
+ * application thread.
+ * </p>
  */
 public final class PrintJob {
 
@@ -48,6 +52,7 @@
      * @return The id.
      */
     public int getId() {
+        PrintService.throwIfNotCalledOnMainThread();
         return mCachedInfo.getId();
     }
 
@@ -62,6 +67,7 @@
      * @return The print job info.
      */
     public PrintJobInfo getInfo() {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isInImmutableState()) {
             return mCachedInfo;
         }
@@ -83,6 +89,7 @@
      * @return The document.
      */
     public PrintDocument getDocument() {
+        PrintService.throwIfNotCalledOnMainThread();
         return mDocument;
     }
 
@@ -96,6 +103,7 @@
      * @see #cancel()
      */
     public boolean isQueued() {
+        PrintService.throwIfNotCalledOnMainThread();
         return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
     }
 
@@ -110,6 +118,7 @@
      * @see #fail(CharSequence)
      */
     public boolean isStarted() {
+        PrintService.throwIfNotCalledOnMainThread();
         return getInfo().getState() == PrintJobInfo.STATE_STARTED;
     }
 
@@ -122,6 +131,7 @@
      * @see #complete()
      */
     public boolean isCompleted() {
+        PrintService.throwIfNotCalledOnMainThread();
         return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
     }
 
@@ -134,6 +144,7 @@
      * @see #fail(CharSequence)
      */
     public boolean isFailed() {
+        PrintService.throwIfNotCalledOnMainThread();
         return getInfo().getState() == PrintJobInfo.STATE_FAILED;
     }
 
@@ -146,6 +157,7 @@
      * @see #cancel()
      */
     public boolean isCancelled() {
+        PrintService.throwIfNotCalledOnMainThread();
         return getInfo().getState() == PrintJobInfo.STATE_FAILED;
     }
 
@@ -158,6 +170,7 @@
      * @see #isQueued()
      */
     public boolean start() {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isQueued()) {
             return setState(PrintJobInfo.STATE_STARTED, null);
         }
@@ -173,6 +186,7 @@
      * @see #isStarted()
      */
     public boolean complete() {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isStarted()) {
             return setState(PrintJobInfo.STATE_COMPLETED, null);
         }
@@ -191,7 +205,8 @@
      * @see #isQueued()
      * @see #isStarted()
      */
-    public boolean fail(CharSequence error) {
+    public boolean fail(String error) {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isQueued() || isStarted()) {
             return setState(PrintJobInfo.STATE_FAILED, error);
         }
@@ -210,6 +225,7 @@
      * @see #isQueued()
      */
     public boolean cancel() {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isQueued() || isStarted()) {
             return setState(PrintJobInfo.STATE_CANCELED, null);
         }
@@ -226,6 +242,7 @@
      * @return True if the tag was set, false otherwise.
      */
     public boolean setTag(String tag) {
+        PrintService.throwIfNotCalledOnMainThread();
         if (isInImmutableState()) {
             return false;
         }
@@ -263,7 +280,7 @@
                 || state == PrintJobInfo.STATE_CANCELED;
     }
 
-    private boolean setState(int state, CharSequence error) {
+    private boolean setState(int state, String error) {
         try {
             if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
                 // Best effort - update the state of the cached info since
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 92bccd4..8fe770c 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -25,7 +25,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintJobInfo;
 import android.print.PrinterId;
 import android.util.Log;
@@ -146,6 +145,11 @@
  * {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService
  * print-service}&gt;</code>.
  * </p>
+ * <p>
+ * <strong>Note: </strong> All callbacks in this class are executed on the main
+ * application thread. You should also invoke any method of this class on the main
+ * application thread.
+ * </p>
  */
 public abstract class PrintService extends Service {
 
@@ -175,14 +179,14 @@
      */
     public static final String SERVICE_META_DATA = "android.printservice";
 
-    private final Object mLock = new Object();
-
     private Handler mHandler;
 
     private IPrintServiceClient mClient;
 
     private int mLastSessionId = -1;
 
+    private PrinterDiscoverySession mDiscoverySession;
+
     @Override
     protected final void attachBaseContext(Context base) {
         super.attachBaseContext(base);
@@ -245,21 +249,18 @@
      * @see PrintJob#isStarted() PrintJob.isStarted()
      */
     public final List<PrintJob> getActivePrintJobs() {
-        final IPrintServiceClient client;
-        synchronized (mLock) {
-            client = mClient;
-        }
-        if (client == null) {
+        throwIfNotCalledOnMainThread();
+        if (mClient == null) {
             return Collections.emptyList();
         }
         try {
             List<PrintJob> printJobs = null;
-            List<PrintJobInfo> printJobInfos = client.getPrintJobInfos();
+            List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
             if (printJobInfos != null) {
                 final int printJobInfoCount = printJobInfos.size();
                 printJobs = new ArrayList<PrintJob>(printJobInfoCount);
                 for (int i = 0; i < printJobInfoCount; i++) {
-                    printJobs.add(new PrintJob(printJobInfos.get(i), client));
+                    printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
                 }
             }
             if (printJobs != null) {
@@ -278,23 +279,50 @@
      * @return Global printer id.
      */
     public final PrinterId generatePrinterId(String localId) {
+        throwIfNotCalledOnMainThread();
         return new PrinterId(new ComponentName(getPackageName(),
                 getClass().getName()), localId);
     }
 
+    static void throwIfNotCalledOnMainThread() {
+        if (!Looper.getMainLooper().isCurrentThread()) {
+            throw new IllegalAccessError("must be called from the main thread");
+        }
+    }
+
     @Override
     public final IBinder onBind(Intent intent) {
         return new IPrintService.Stub() {
             @Override
-            public void setClient(IPrintServiceClient client) {
-                mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
-                        .sendToTarget();
+            public void createPrinterDiscoverySession() {
+                mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
             }
 
             @Override
-            public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
-                mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION,
-                        observer).sendToTarget();
+            public void destroyPrinterDiscoverySession() {
+                mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+            }
+
+            public void startPrinterDiscovery(List<PrinterId> priorityList) {
+                mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
+                        priorityList).sendToTarget();
+            }
+
+            @Override
+            public void stopPrinterDiscovery() {
+                mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY);
+            }
+
+            @Override
+            public void requestPrinterUpdate(PrinterId printerId) {
+                mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE,
+                        printerId).sendToTarget();
+            }
+
+            @Override
+            public void setClient(IPrintServiceClient client) {
+                mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
+                        .sendToTarget();
             }
 
             @Override
@@ -312,33 +340,62 @@
     }
 
     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_SET_CLEINT = 4;
+        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+        public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+        public static final int MSG_START_PRINTER_DISCOVERY = 3;
+        public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+        public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+        public static final int MSG_ON_PRINTJOB_QUEUED = 6;
+        public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 7;
+        public static final int MSG_SET_CLEINT = 8;
 
         public 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_CREATE_PRINTER_DISCOVERY_SESSION: {
-                    IPrinterDiscoverySessionObserver observer =
-                            (IPrinterDiscoverySessionObserver) message.obj;
+                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
                     PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
                     if (session == null) {
                         throw new NullPointerException("session cannot be null");
                     }
-                    synchronized (mLock) {
-                        if (session.getId() == mLastSessionId) {
-                            throw new IllegalStateException("cannot reuse sessions");
-                        }
-                        mLastSessionId = session.getId();
+                    if (session.getId() == mLastSessionId) {
+                        throw new IllegalStateException("cannot reuse session instances");
                     }
-                    session.setObserver(observer);
+                    mDiscoverySession = session;
+                    mLastSessionId = session.getId();
+                    session.setObserver(mClient);
+                } break;
+
+                case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+                    if (mDiscoverySession != null) {
+                        mDiscoverySession.destroy();
+                        mDiscoverySession = null;
+                    }
+                } break;
+
+                case MSG_START_PRINTER_DISCOVERY: {
+                    if (mDiscoverySession != null) {
+                        List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+                        mDiscoverySession.startPrinterDiscovery(priorityList);
+                    }
+                } break;
+
+                case MSG_STOP_PRINTER_DISCOVERY: {
+                    if (mDiscoverySession != null) {
+                        mDiscoverySession.stopPrinterDiscovery();
+                    }
+                } break;
+
+                case MSG_REQUEST_PRINTER_UPDATE: {
+                    if (mDiscoverySession != null) {
+                        PrinterId printerId = (PrinterId) message.obj;
+                        mDiscoverySession.requestPrinterUpdate(printerId);
+                    }
                 } break;
 
                 case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
@@ -352,15 +409,12 @@
                 } break;
 
                 case MSG_SET_CLEINT: {
-                    IPrintServiceClient client = (IPrintServiceClient) message.obj;
-                    synchronized (mLock) {
-                        mClient = client;
-                    }
-                    if (client != null) {
+                    mClient = (IPrintServiceClient) message.obj;
+                    if (mClient != null) {
                         onConnected();
                      } else {
                         onDisconnected();
-                    }
+                     }
                 } break;
 
                 default: {
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 92dc0dd..8b959a6 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -16,18 +16,15 @@
 
 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.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
+import android.util.ArrayMap;
 import android.util.Log;
 
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -36,67 +33,75 @@
  * 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)}.
+ * During the lifetime of this session you may be asked to start and stop
+ * performing printer discovery multiple times. You will receive a call to {@link
+ * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer
+ * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()}
+ * to stop printer discovery. When the system is no longer interested in printers
+ * discovered by this session you will receive a call to {@link #onDestroy()} at
+ * which point the system will no longer call into the session and all the session
+ * methods will do nothing.
+ * </p>
+ * <p>
+ * 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)}. The printers added in this
+ * session can be acquired via {@link #getPrinters()} where the returned printers
+ * will be an up-to-date snapshot of the printers that you reported during the
+ * session. Printers are <strong>not</strong> persisted across sessions.
  * </p>
  * <p>
  * The system will make a call to
  * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you
  * need to update a given printer. It is possible that you add a printer without
- * 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.
+ * specifying its capabilities. This enables you to avoid querying all discovered
+ * printers for their capabilities, rather querying the capabilities of a printer
+ * only if necessary. For example, the system will request that you update a printer
+ * if it gets selected by the user. If you did not report the printer capabilities
+ * when adding it, you must do so after the system requests a printer update.
+ * Otherwise, the printer will be ignored.
  * </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.
+ * <strong>Note: </strong> All callbacks in this class are executed on the main
+ * application thread. You also have to invoke any method of this class on the main
+ * application thread.
  * </p>
  */
 public abstract class PrinterDiscoverySession {
     private static final String LOG_TAG = "PrinterDiscoverySession";
 
+    private static final int MAX_ITEMS_PER_CALLBACK = 100;
+
     private static int sIdCounter = 0;
 
-    private final Object mLock = new Object();
-
-    private final Handler mHandler;
-
     private final int mId;
 
-    private IPrinterDiscoverySessionController mController;
+    private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
+            new ArrayMap<PrinterId, PrinterInfo>();
 
-    private IPrinterDiscoverySessionObserver mObserver;
+    private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters;
+
+    private IPrintServiceClient mObserver;
+
+    private boolean mIsDestroyed;
+
+    private boolean mIsDiscoveryStarted;
 
     /**
      * Constructor.
-     *
-     * @param context A context instance.
      */
-    public PrinterDiscoverySession(Context context) {
+    public PrinterDiscoverySession() {
         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);
-            }
+    void setObserver(IPrintServiceClient observer) {
+        mObserver = observer;
+        // If some printers were added in the method that
+        // created the session, send them over.
+        if (!mPrinters.isEmpty()) {
+            sendAddedPrinters(mObserver, getPrinters());
         }
     }
 
@@ -105,131 +110,357 @@
     }
 
     /**
+     * Gets the printers reported in this session. For example, if you add two
+     * printers and remove one of them, the returned list will contain only
+     * the printer that was added but not removed.
+     * <p>
+     * <strong>Note: </strong> Calls to this method after the session is
+     * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+     * </p>
+     *
+     * @return The printers.
+     *
+     * @see #addPrinters(List)
+     * @see #removePrinters(List)
+     * @see #updatePrinters(List)
+     * @see #isDestroyed()
+     */
+    public final List<PrinterInfo> getPrinters() {
+        PrintService.throwIfNotCalledOnMainThread();
+        if (mIsDestroyed) {
+            return Collections.emptyList();
+        }
+        return new ArrayList<PrinterInfo>(mPrinters.values());
+    }
+
+    /**
      * 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.
+     * times during the life of this session. Duplicates will be ignored.
      * <p>
-     * <strong>Note: </strong> Calls to this method before the session is opened,
-     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
-     * i.e. after the call to {@link #onClose()}, will be ignored.
+     * <strong>Note: </strong> Calls to this method after the session is
+     * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
      * </p>
      *
      * @param printers The printers to add.
      *
      * @see #removePrinters(List)
      * @see #updatePrinters(List)
+     * @see #getPrinters()
+     * @see #isDestroyed()
      */
     public final void addPrinters(List<PrinterInfo> printers) {
-        final IPrinterDiscoverySessionObserver observer;
-        synchronized (mLock) {
-            observer = mObserver;
+        PrintService.throwIfNotCalledOnMainThread();
+
+        // If the session is destroyed - nothing do to.
+        if (mIsDestroyed) {
+            Log.w(LOG_TAG, "Not adding printers - session destroyed.");
+            return;
         }
-        if (observer != null) {
-            try {
-                observer.onPrintersAdded(printers);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error adding printers", re);
+
+        if (mIsDiscoveryStarted) {
+            // If during discovery, add the new printers and send them.
+            List<PrinterInfo> addedPrinters = new ArrayList<PrinterInfo>();
+            final int addedPrinterCount = printers.size();
+            for (int i = 0; i < addedPrinterCount; i++) {
+                PrinterInfo addedPrinter = printers.get(i);
+                if (mPrinters.get(addedPrinter.getId()) == null) {
+                    mPrinters.put(addedPrinter.getId(), addedPrinter);
+                    addedPrinters.add(addedPrinter);
+                }
+            }
+
+            // Send the added printers, if such.
+            if (!addedPrinters.isEmpty()) {
+                sendAddedPrinters(mObserver, addedPrinters);
             }
         } else {
-            Log.w(LOG_TAG, "Printer discovery session not open not adding printers.");
+            // Remember the last sent printers if needed.
+            if (mLastSentPrinters == null) {
+                mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+            }
+
+            // Update the printers.
+            final int addedPrinterCount = printers.size();
+            for (int i = 0; i < addedPrinterCount; i++) {
+                PrinterInfo addedPrinter = printers.get(i);
+                if (mPrinters.get(addedPrinter.getId()) == null) {
+                    mPrinters.put(addedPrinter.getId(), addedPrinter);
+                }
+            }
+        }
+    }
+
+    private static void sendAddedPrinters(IPrintServiceClient observer,
+        List<PrinterInfo> printers) {
+        try {
+            final int printerCount = printers.size();
+            if (printerCount <= MAX_ITEMS_PER_CALLBACK) {
+                observer.onPrintersAdded(printers);
+            } else {
+                // Send the added printers in chunks avoiding the binder transaction limit.
+                final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1;
+                for (int i = 0; i < transactionCount; i++) {
+                    final int start = i * MAX_ITEMS_PER_CALLBACK;
+                    final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount);
+                    List<PrinterInfo> subPrinters = printers.subList(start, end);
+                    observer.onPrintersAdded(subPrinters);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error sending added 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.
+     * printer has no effect. Removed printers can be added again. You can
+     * call this method multiple times during the lifetime of this session.
      * <p>
-     * <strong>Note: </strong> Calls to this method before the session is opened,
-     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
-     * i.e. after the call to {@link #onClose()}, will be ignored.
+     * <strong>Note: </strong> Calls to this method after the session is
+     * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
      * </p>
      *
      * @param printerIds The ids of the removed printers.
      *
      * @see #addPrinters(List)
      * @see #updatePrinters(List)
+     * @see #getPrinters()
+     * @see #isDestroyed()
      */
     public final void removePrinters(List<PrinterId> printerIds) {
-        final IPrinterDiscoverySessionObserver observer;
-        synchronized (mLock) {
-            observer = mObserver;
+        PrintService.throwIfNotCalledOnMainThread();
+
+        // If the session is destroyed - nothing do to.
+        if (mIsDestroyed) {
+            Log.w(LOG_TAG, "Not removing printers - session destroyed.");
+            return;
         }
-        if (observer != null) {
-            try {
-                observer.onPrintersRemoved(printerIds);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error removing printers", re);
+
+        if (mIsDiscoveryStarted) {
+            // If during discovery, remove existing printers and send them.
+            List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>();
+            final int removedPrinterIdCount = printerIds.size();
+            for (int i = 0; i < removedPrinterIdCount; i++) {
+                PrinterId removedPrinterId = printerIds.get(i);
+                if (mPrinters.remove(removedPrinterId) != null) {
+                    removedPrinterIds.add(removedPrinterId);
+                }
+            }
+
+            // Send the removed printers, if such.
+            if (!removedPrinterIds.isEmpty()) {
+                sendRemovedPrinters(mObserver, removedPrinterIds);
             }
         } else {
-            Log.w(LOG_TAG, "Printer discovery session not open not removing printers.");
+            // Remember the last sent printers if needed.
+            if (mLastSentPrinters == null) {
+                mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+            }
+
+            // Update the printers.
+            final int removedPrinterIdCount = printerIds.size();
+            for (int i = 0; i < removedPrinterIdCount; i++) {
+                PrinterId removedPrinterId = printerIds.get(i);
+                mPrinters.remove(removedPrinterId);
+            }
+        }
+    }
+
+    private static void sendRemovedPrinters(IPrintServiceClient observer,
+            List<PrinterId> printerIds) {
+        try {
+            final int printerIdCount = printerIds.size();
+            if (printerIdCount <= MAX_ITEMS_PER_CALLBACK) {
+                observer.onPrintersRemoved(printerIds);
+            } else {
+                final int transactionCount = (printerIdCount / MAX_ITEMS_PER_CALLBACK) + 1;
+                for (int i = 0; i < transactionCount; i++) {
+                    final int start = i * MAX_ITEMS_PER_CALLBACK;
+                    final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerIdCount);
+                    List<PrinterId> subPrinterIds = printerIds.subList(start, end);
+                    observer.onPrintersRemoved(subPrinterIds);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error sending removed 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.
+     * during the lifetime of this session.
      * <p>
-     * <strong>Note: </strong> Calls to this method before the session is opened,
-     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
-     * i.e. after the call to {@link #onClose()}, will be ignored.
+     * <strong>Note: </strong> Calls to this method after the session is
+     * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
      * </p>
      *
      * @param printers The printers to update.
      *
      * @see #addPrinters(List)
      * @see #removePrinters(List)
+     * @see #getPrinters()
+     * @see #isDestroyed()
      */
     public final void updatePrinters(List<PrinterInfo> printers) {
-        final IPrinterDiscoverySessionObserver observer;
-        synchronized (mLock) {
-            observer = mObserver;
+        PrintService.throwIfNotCalledOnMainThread();
+
+        // If the session is destroyed - nothing do to.
+        if (mIsDestroyed) {
+            Log.w(LOG_TAG, "Not updating printers - session destroyed.");
+            return;
         }
-        if (observer != null) {
-            try {
-                observer.onPrintersUpdated(printers);
-            } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error updating printers", re);
+
+        if (mIsDiscoveryStarted) {
+            // If during discovery, update existing printers and send them.
+            List<PrinterInfo> updatedPrinters = new ArrayList<PrinterInfo>();
+            final int updatedPrinterCount = printers.size();
+            for (int i = 0; i < updatedPrinterCount; i++) {
+                PrinterInfo updatedPrinter = printers.get(i);
+                PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId());
+                if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) {
+                    mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+                    updatedPrinters.add(updatedPrinter);
+                }
+            }
+
+            // Send the updated printers, if such.
+            if (!updatedPrinters.isEmpty()) {
+                sendUpdatedPrinters(mObserver, updatedPrinters);
             }
         } else {
-            Log.w(LOG_TAG, "Printer discovery session not open not updating printers.");
+            // Remember the last sent printers if needed.
+            if (mLastSentPrinters == null) {
+                mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+            }
+
+            // Update the printers.
+            final int updatedPrinterCount = printers.size();
+            for (int i = 0; i < updatedPrinterCount; i++) {
+                PrinterInfo updatedPrinter = printers.get(i);
+                PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId());
+                if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) {
+                    mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+                }
+            }
         }
     }
 
+    private static void sendUpdatedPrinters(IPrintServiceClient observer,
+            List<PrinterInfo> printers) {
+        try {
+            final int printerCount = printers.size();
+            if (printerCount <= MAX_ITEMS_PER_CALLBACK) {
+                observer.onPrintersUpdated(printers);
+            } else {
+                final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1;
+                for (int i = 0; i < transactionCount; i++) {
+                    final int start = i * MAX_ITEMS_PER_CALLBACK;
+                    final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount);
+                    List<PrinterInfo> subPrinters = printers.subList(start, end);
+                    observer.onPrintersUpdated(subPrinters);
+                }
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error sending updated printers", re);
+        }
+    }
+
+    private void sendOutOfDiscoveryPeriodPrinterChanges() {
+        // Noting changed since the last discovery period - nothing to do.
+        if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) {
+            mLastSentPrinters = null;
+            return;
+        }
+
+        List<PrinterInfo> addedPrinters = null;
+        List<PrinterInfo> updatedPrinters = null;
+        List<PrinterId> removedPrinterIds = null;
+
+        // Determine the added and updated printers.
+        for (PrinterInfo printer : mPrinters.values()) {
+            PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId());
+            if (sentPrinter != null) {
+                if (!sentPrinter.equals(printer)) {
+                    if (updatedPrinters == null) {
+                        updatedPrinters = new ArrayList<PrinterInfo>();
+                    }
+                    updatedPrinters.add(printer);
+                }
+            } else {
+                if (addedPrinters == null) {
+                    addedPrinters = new ArrayList<PrinterInfo>();
+                }
+                addedPrinters.add(printer);
+            }
+        }
+
+        // Send the added printers, if such.
+        if (addedPrinters != null) {
+            sendAddedPrinters(mObserver, addedPrinters);
+        }
+
+        // Send the updated printers, if such.
+        if (updatedPrinters != null) {
+            sendUpdatedPrinters(mObserver, updatedPrinters);
+        }
+
+        // Determine the removed printers.
+        for (PrinterInfo sentPrinter : mLastSentPrinters.values()) {
+            if (!mPrinters.containsKey(sentPrinter.getId())) {
+                if (removedPrinterIds == null) {
+                    removedPrinterIds = new ArrayList<PrinterId>();
+                }
+                removedPrinterIds.add(sentPrinter.getId());
+            }
+        }
+
+        // Send the removed printers, if such.
+        if (removedPrinterIds != null) {
+            sendRemovedPrinters(mObserver, removedPrinterIds);
+        }
+
+        mLastSentPrinters = null;
+    }
+
     /**
-     * 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()}.
+     * Callback asking you to 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 changed should be updated via calling {@link
+     * #updatePrinters(List)}. You will receive a call to call to {@link
+     * #onStopPrinterDiscovery()} when you should stop printer discovery.
      * <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.
+     * During the lifetime of this session all printers that are known to your print
+     * service have to be added. The system does not retain any printers across sessions.
+     * However, if you were asked to start and then stop performing printer discovery
+     * in this session, then a subsequent discovering should not re-discover already
+     * discovered printers.
      * </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.
+     * <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()
+     * @param priorityList The list of printers to validate first. Never null.
+     *
+     * @see #onStopPrinterDiscovery()
      * @see #addPrinters(List)
      * @see #removePrinters(List)
      * @see #updatePrinters(List)
+     * @see #isPrinterDiscoveryStarted()
      */
-    public abstract void onOpen(List<PrinterId> priorityList);
+    public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
 
     /**
-     * Callback notifying you that the session is closed and you should stop
-     * printer discovery. After the session is closed any call to the methods
-     * of this instance will be ignored. Once the session is closed
-     * it will never be opened again.
+     * Callback notifying you that you should stop printer discovery.
+     *
+     * @see #onStartPrinterDiscovery(List)
+     * @see #isPrinterDiscoveryStarted()
      */
-    public abstract void onClose();
+    public abstract void onStopPrinterDiscovery();
 
     /**
      * Requests that you update a printer. You are responsible for updating
@@ -255,77 +486,72 @@
      */
     public abstract void onRequestPrinterUpdate(PrinterId printerId);
 
-    void close() {
-        synchronized (mLock) {
-            mController = null;
+    /**
+     * Notifies you that the session is destroyed. After this callback is invoked
+     * any calls to the methods of this class will be ignored, {@link #isDestroyed()}
+     * will return true and you will also no longer receive callbacks.
+     *
+     * @see #isDestroyed()
+     */
+    public abstract void onDestroy();
+
+    /**
+     * Gets whether the session is destroyed.
+     *
+     * @return Whether the session is destroyed.
+     *
+     * @see #onDestroy()
+     */
+    public final boolean isDestroyed() {
+        PrintService.throwIfNotCalledOnMainThread();
+        return mIsDestroyed;
+    }
+
+    /**
+     * Gets whether printer discovery is started.
+     *
+     * @return Whether printer discovery is destroyed.
+     *
+     * @see #onStartPrinterDiscovery(List)
+     * @see #onStopPrinterDiscovery()
+     */
+    public final boolean isPrinterDiscoveryStarted() {
+        PrintService.throwIfNotCalledOnMainThread();
+        return mIsDiscoveryStarted;
+    }
+
+    void startPrinterDiscovery(List<PrinterId> priorityList) {
+        if (!mIsDestroyed) {
+            mIsDiscoveryStarted = true;
+            sendOutOfDiscoveryPeriodPrinterChanges();
+            if (priorityList == null) {
+                priorityList = Collections.emptyList();
+            }
+            onStartPrinterDiscovery(priorityList);
+        }
+    }
+
+    void stopPrinterDiscovery() {
+        if (!mIsDestroyed) {
+            mIsDiscoveryStarted = false;
+            onStopPrinterDiscovery();
+        }
+    }
+
+    void requestPrinterUpdate(PrinterId printerId) {
+        if (!mIsDestroyed) {
+            onRequestPrinterUpdate(printerId);
+        }
+    }
+
+    void destroy() {
+        if (!mIsDestroyed) {
+            mIsDestroyed = true;
+            mIsDiscoveryStarted = false;
+            mPrinters.clear();
+            mLastSentPrinters = null;
             mObserver = null;
+            onDestroy();
         }
     }
-
-    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/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index b442ff5..d9e3ef6 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -65,7 +65,11 @@
         
         mH.sendMessage(msg);
     }
-    
+
+    public void sendMessageDelayed(Message msg, long delayMillis) {
+        mH.sendMessageDelayed(msg, delayMillis);
+    }
+
     public boolean hasMessages(int what) {
         return mH.hasMessages(what);
     }
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index c00639d..1f10af8 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -18,7 +18,7 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.printspooler"
-        android:sharedUserId="android.uid.printspooler"
+        android:sharedUserId="android.uid.system"
         android:versionName="1"
         android:versionCode="1"
         coreApp="true">
@@ -51,9 +51,10 @@
         </activity>
 
         <activity
-            android:name=".ChoosePrinterActivity"
-            android:exported="false"
-            android:theme="@android:style/Theme.Holo.Light">
+            android:name=".SelectPrinterActivity"
+            android:label="@string/all_printers_label"
+            android:theme="@style/SelectPrinterActivityTheme"
+            android:exported="false">
         </activity>
 
         <receiver
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 0000000..4b68f52
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 0000000..15ffadd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
new file mode 100644
index 0000000..420510e
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/layout/choose_printer_activity.xml b/packages/PrintSpooler/res/layout/choose_printer_activity.xml
deleted file mode 100644
index c34a108..0000000
--- a/packages/PrintSpooler/res/layout/choose_printer_activity.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/list_view"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical">
-</ListView>
-
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
index a0c111b..7817094 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
@@ -20,9 +20,4 @@
     android:layout_height="wrap_content"
     android:layout_gravity="center"
     android:background="@color/container_background">
-
-    <include
-        layout="@layout/print_job_config_activity_content_editing">
-    </include>
-
 </FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
new file mode 100644
index 0000000..f4e1853
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <fragment
+        android:name="com.android.printspooler.SelectPrinterFragment"
+        android:id="@+id/select_printer_fragment"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+    </fragment>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index 002cc14..d14c064 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:paddingStart="8dip"
     android:paddingEnd="8dip"
diff --git a/packages/PrintSpooler/res/menu/choose_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
similarity index 79%
rename from packages/PrintSpooler/res/menu/choose_printer_activity.xml
rename to packages/PrintSpooler/res/menu/select_printer_activity.xml
index 3774279..28fbd35 100644
--- a/packages/PrintSpooler/res/menu/choose_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -23,7 +23,15 @@
         android:actionViewClass="android.widget.SearchView"
         android:showAsAction="ifRoom"
         android:alphabeticShortcut="f"
-         android:imeOptions="actionSearch">
+        android:imeOptions="actionSearch">
+    </item>
+
+    <item
+        android:id="@+id/action_add_printer"
+        android:title="@null"
+        android:icon="@drawable/ic_menu_add"
+        android:showAsAction="ifRoom"
+        android:alphabeticShortcut="a">
     </item>
 
 </menu>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 1cd611f..41fc516 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -58,11 +58,32 @@
     <!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
     <string name="generating_print_job">Generating print job</string>
 
-    <!-- Choose printer activity -->
+    <!-- Title for the save as PDF option in the printer list. [CHAR LIMIT=30] -->
+    <string name="save_as_pdf">Save as PDF</string>
+
+    <!-- Title for the open all printers UI option in the printer list. [CHAR LIMIT=30] -->
+    <string name="all_printers">All printers\.\.\.</string>
+
+    <!-- Title for the searching for printers option in the printer list
+         (only option if not printers are available). [CHAR LIMIT=40] -->
+    <string name="searching_for_printers">Searching for printers\.\.\.</string>
+
+    <!-- Select printer activity -->
 
     <!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
     <string name="search">Search</string>
 
+    <!-- Title for the select printer activity. [CHAR LIMIT=30] -->
+    <string name="all_printers_label">All printers</string>
+
+    <!-- Add printer dialog  -->
+
+    <!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
+    <string name="choose_print_service">Choose print service</string>
+
+    <!-- Title for the button to search the play store for print services. [CHAR LIMIT=50] -->
+    <string name="search_play_store">Search in play store</string>
+
     <!-- Notifications -->
 
     <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index ab16c65..831b0ec 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -24,4 +24,12 @@
         <item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
     </style>
 
+    <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.Holo.Light">
+        <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
+    </style>
+
+    <style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
+        <item name="android:displayOptions">showTitle</item>
+    </style>
+
 </resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
deleted file mode 100644
index 658a224..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-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.ArraySet;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class is responsible to provide the available printers.
- * It starts and stops printer discovery and manages the returned
- * printers.
- */
-public class AvailablePrinterProvider extends DataProvider<PrinterInfo>
-        implements DataLoader {
-    private static final String LOG_TAG = "AvailablePrinterProvider";
-
-    private final Set<PrinterId> mPrinteIdsSet = new ArraySet<PrinterId>();
-
-    private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
-    private final List<PrinterId> mPriorityList;
-
-    private PrinterDiscoverySession mDiscoverySession;
-
-    public AvailablePrinterProvider(Context context, List<PrinterId> priorityList) {
-        mDiscoverySession = new PrinterDiscoverySession(context.getMainLooper());
-        mPriorityList = priorityList;
-    }
-
-    @Override
-    public void startLoadData() {
-        mDiscoverySession.open();
-    }
-
-    @Override
-    public void stopLoadData() {
-        mDiscoverySession.close();
-    }
-
-    @Override
-    public int getItemCount() {
-        return mPrinters.size();
-    }
-
-    @Override
-    public int getItemIndex(PrinterInfo printer) {
-        return mPrinters.indexOf(printer);
-    }
-
-    @Override
-    public PrinterInfo getItemAt(int index) {
-        return mPrinters.get(index);
-    }
-
-    public void refreshItem(int index) {
-        PrinterInfo printer = getItemAt(index);
-        mDiscoverySession.requestPrinterUpdate(printer.getId());
-    }
-
-    private void addPrinters(List<PrinterInfo> printers) {
-        boolean addedPrinters = false;
-
-        final int addedPrinterCount = printers.size();
-        for (int i = 0; i < addedPrinterCount; i++) {
-           PrinterInfo addedPrinter = printers.get(i);
-           if (mPrinteIdsSet.add(addedPrinter.getId())) {
-               mPrinters.add(addedPrinter);
-               addedPrinters = true;
-           }
-        }
-
-        if (addedPrinters) {
-            notifyChanged();
-        }
-    }
-
-    private void updatePrinters(List<PrinterInfo> printers) {
-        boolean updatedPrinters = false;
-
-        final int updatedPrinterCount = printers.size();
-        for (int i = 0; i < updatedPrinterCount; i++) {
-            PrinterInfo updatedPrinter = printers.get(i);
-            if (mPrinteIdsSet.contains(updatedPrinter.getId())) {
-                final int oldPrinterCount = mPrinters.size();
-                for (int j = 0; j < oldPrinterCount; j++) {
-                    PrinterInfo oldPrinter = mPrinters.get(j);
-                    if (updatedPrinter.getId().equals(oldPrinter.getId())) {
-                        mPrinters.set(j, updatedPrinter);
-                        updatedPrinters = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (updatedPrinters) {
-            notifyChanged();
-        }
-    }
-
-    private void removePrinters(List<PrinterId> printers) {
-        boolean removedPrinters = false;
-
-        final int removedPrinterCount = printers.size();
-        for (int i = 0; i < removedPrinterCount; i++) {
-            PrinterId removedPrinter = printers.get(i);
-            if (mPrinteIdsSet.contains(removedPrinter)) {
-                mPrinteIdsSet.remove(removedPrinter);
-                Iterator<PrinterInfo> iterator = mPrinters.iterator();
-                while (iterator.hasNext()) {
-                    PrinterInfo oldPrinter = iterator.next();
-                    if (removedPrinter.equals(oldPrinter.getId())) {
-                        iterator.remove();
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (removedPrinters) {
-            notifyChanged();
-        }
-    }
-
-    private final class PrinterDiscoverySession {
-
-        private final Handler mHandler;
-
-        private final IPrinterDiscoverySessionObserver mObserver;
-
-        private IPrinterDiscoverySessionController mController;
-
-        public PrinterDiscoverySession(Looper looper) {
-            mHandler = new SessionHandler(looper);
-            mObserver = new PrinterDiscoverySessionObserver(this);
-        }
-
-        public void open() {
-            PrintSpooler.peekInstance().createPrinterDiscoverySession(
-                    mObserver);
-        }
-
-        public void close() {
-            if (mController != null) {
-                try {
-                    mController.close();
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error closing printer discovery session", re);
-                } finally {
-                    mController = null;
-                }
-            }
-        }
-
-        public void requestPrinterUpdate(PrinterId printerId) {
-            if (mController != null) {
-                try {
-                    mController.requestPrinterUpdate(printerId);
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error requesting printer udpdate", re);
-                }
-            }
-        }
-
-        private final class SessionHandler extends Handler {
-            public static final int MSG_SET_CONTROLLER = 1;
-            public static final int MSG_ON_PRINTERS_ADDED = 2;
-            public static final int MSG_ON_PRINTERS_REMOVED = 3;
-            public static final int MSG_ON_PRINTERS_UPDATED = 4;
-
-            public SessionHandler(Looper looper) {
-                super(looper, null, false);
-            }
-
-            @Override
-            @SuppressWarnings("unchecked")
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_SET_CONTROLLER: {
-                        mController = (IPrinterDiscoverySessionController) message.obj;
-                        try {
-                            mController.open(mPriorityList);
-                        } catch (RemoteException e) {
-                            Log.e(LOG_TAG, "Error starting printer discovery");
-                        }
-                    } break;
-
-                    case MSG_ON_PRINTERS_ADDED: {
-                        List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
-                        addPrinters(printers);
-                    } break;
-
-                    case MSG_ON_PRINTERS_REMOVED: {
-                        List<PrinterId> printers = (List<PrinterId>) message.obj;
-                        removePrinters(printers);
-                    } break;
-
-                    case MSG_ON_PRINTERS_UPDATED: {
-                        List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
-                        updatePrinters(printers);
-                    } break;
-                };
-            }
-        }
-    }
-
-    private static final class PrinterDiscoverySessionObserver
-            extends IPrinterDiscoverySessionObserver.Stub {
-
-        private final WeakReference<PrinterDiscoverySession> mWeakSession;
-
-        public PrinterDiscoverySessionObserver(PrinterDiscoverySession session) {
-            mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
-        }
-
-        @Override
-        public void setController(IPrinterDiscoverySessionController controller) {
-            PrinterDiscoverySession sesison = mWeakSession.get();
-            if (sesison != null) {
-                sesison.mHandler.obtainMessage(
-                        PrinterDiscoverySession.SessionHandler.MSG_SET_CONTROLLER,
-                        controller).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onPrintersAdded(List<PrinterInfo> printers) {
-            PrinterDiscoverySession sesison = mWeakSession.get();
-            if (sesison != null) {
-                sesison.mHandler.obtainMessage(
-                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_ADDED,
-                        printers).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onPrintersRemoved(List<PrinterId> printers) {
-            PrinterDiscoverySession session = mWeakSession.get();
-            if (session != null) {
-                session.mHandler.obtainMessage(
-                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_REMOVED,
-                        printers).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onPrintersUpdated(List<PrinterInfo> printers) {
-            PrinterDiscoverySession session = mWeakSession.get();
-            if (session != null) {
-                session.mHandler.obtainMessage(
-                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_UPDATED,
-                        printers).sendToTarget();
-            }
-        }
-    };
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
deleted file mode 100644
index 8b0dd66a..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class ChoosePrinterActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle bundle) {
-        setContentView(R.layout.choose_printer_activity);
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java b/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
deleted file mode 100644
index 82cc2e1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-/**
- * This is the contract for a class that know how to load data.
- */
-public interface DataLoader {
-
-    /**
-     * Requests to start loading data.
-     */
-    public void startLoadData();
-
-    /**
-     * Requests to stop loading data.
-     */
-    public void stopLoadData();
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java b/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
deleted file mode 100644
index 7b10903..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-import android.database.DataSetObservable;
-
-/**
- * This is the simple contract for data providers.
- *
- * @param <T> The type of the providers data.
- */
-public abstract class DataProvider<T> extends DataSetObservable {
-
-    /**
-     * Gets the number of items.
-     *
-     * @return The item count.
-     */
-    public abstract int getItemCount();
-
-    /**
-     * Gets the index of an item.
-     *
-     * @param item The item.
-     * @return The item index.
-     */
-    public abstract int getItemIndex(T item);
-
-    /**
-     * Gets an item at a given position.
-     *
-     * @param index The position.
-     * @return The item.
-     */
-    public abstract T getItemAt(int index);
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
deleted file mode 100644
index 2c539d1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class provides the favorite printers based on past usage.
- */
-final class FavoritePrinterProvider extends DataProvider<PrinterInfo> implements DataLoader {
-
-    private static final String LOG_TAG = "FavoritePrinterProvider";
-
-    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
-
-    private static final int MAX_HISTORY_LENGTH = 50;
-
-    private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
-
-    private final List<PrinterRecord> mHistoricalPrinters = new ArrayList<PrinterRecord>();
-
-    private final List<PrinterRecord> mFavoritePrinters = new ArrayList<PrinterRecord>();
-
-    private final PersistenceManager mPersistenceManager;
-
-    public FavoritePrinterProvider(Context context) {
-        mPersistenceManager = new PersistenceManager(context);
-    }
-
-    public void addPrinter(PrinterInfo printer) {
-        addPrinterInternal(printer);
-        computeFavoritePrinters();
-        mPersistenceManager.writeState();
-    }
-
-    @Override
-    public int getItemCount() {
-        return mFavoritePrinters.size();
-    }
-
-    @Override
-    public PrinterInfo getItemAt(int index) {
-        return mFavoritePrinters.get(index).printer;
-    }
-
-    @Override
-    public int getItemIndex(PrinterInfo printer) {
-        return mFavoritePrinters.indexOf(printer);
-    }
-
-    @Override
-    public void startLoadData() {
-        mPersistenceManager.readStateLocked();
-        computeFavoritePrinters();
-    }
-
-    @Override
-    public void stopLoadData() {
-        /* do nothing */
-    }
-
-    private void addPrinterInternal(PrinterInfo printer) {
-        if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
-            mHistoricalPrinters.remove(0);
-        }
-        mHistoricalPrinters.add(new PrinterRecord(printer));
-    }
-
-    private void computeFavoritePrinters() {
-        Map<PrinterId, PrinterRecord> recordMap =
-                new ArrayMap<PrinterId, PrinterRecord>();
-
-        // Recompute the weights.
-        float currentWeight = 1.0f;
-        final int printerCount = mHistoricalPrinters.size();
-        for (int i = printerCount - 1; i >= 0; i--) {
-            PrinterRecord record = mHistoricalPrinters.get(i);
-            record.weight = currentWeight;
-            // Aggregate weight for the same printer
-            PrinterRecord oldRecord = recordMap.put(record.printer.getId(), record);
-            if (oldRecord != null) {
-                record.weight += oldRecord.weight;
-            }
-            currentWeight *= WEIGHT_DECAY_COEFFICIENT;
-        }
-
-        // Copy the unique printer records with computed weights.
-        mFavoritePrinters.addAll(recordMap.values());
-
-        // Soft the favorite printers.
-        Collections.sort(mFavoritePrinters);
-    }
-
-    private final class PrinterRecord implements Comparable<PrinterRecord> {
-        public final PrinterInfo printer;
-        public float weight;
-
-        public PrinterRecord(PrinterInfo printer) {
-            this.printer = printer;
-        }
-
-        @Override
-        public int compareTo(PrinterRecord another) {
-            return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
-        }
-    }
-
-    private final class PersistenceManager {
-        private static final String PERSIST_FILE_NAME = "printer_history.xml";
-
-        private static final String TAG_PRINTERS = "printers";
-
-        private static final String TAG_PRINTER = "printer";
-        private static final String TAG_PRINTER_ID = "printerId";
-
-        private static final String ATTR_LOCAL_ID = "localId";
-        private static final String ATTR_SERVICE_NAME = "serviceName";
-
-        private static final String ATTR_NAME = "name";
-        private static final String ATTR_DESCRIPTION = "description";
-        private static final String ATTR_STATUS = "status";
-
-        private final AtomicFile mStatePersistFile;
-
-        private PersistenceManager(Context context) {
-            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
-                    PERSIST_FILE_NAME));
-        }
-
-        @SuppressWarnings("unchecked")
-        public void writeState() {
-
-            new AsyncTask<List<PrinterRecord>, Void, Void>() {
-                @Override
-                protected Void doInBackground(List<PrinterRecord>... printers) {
-                    doWriteState(printers[0]);
-                    return null;
-                }
-
-                @Override
-                protected void onPostExecute(Void result) {
-                    notifyChanged();
-                }
-
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                    new ArrayList<PrinterRecord>(mHistoricalPrinters));
-        }
-
-        private void doWriteState(List<PrinterRecord> printers) {
-            FileOutputStream out = null;
-            try {
-                out = mStatePersistFile.startWrite();
-
-                XmlSerializer serializer = new FastXmlSerializer();
-                serializer.setOutput(out, "utf-8");
-                serializer.startDocument(null, true);
-                serializer.startTag(null, TAG_PRINTERS);
-
-                final int printerCount = printers.size();
-                for (int i = printerCount - 1; i >= 0; i--) {
-                    PrinterInfo printer = printers.get(i).printer;
-
-                    serializer.startTag(null, TAG_PRINTER);
-
-                    serializer.attribute(null, ATTR_NAME, printer.getName());
-                    serializer.attribute(null, ATTR_STATUS, String.valueOf(printer.getStatus()));
-                    String description = printer.getDescription();
-                    if (description != null) {
-                        serializer.attribute(null, ATTR_DESCRIPTION, description);
-                    }
-
-                    PrinterId printerId = printer.getId();
-                    serializer.startTag(null, TAG_PRINTER_ID);
-                    serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
-                    serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
-                            .flattenToString());
-                    serializer.endTag(null, TAG_PRINTER_ID);
-
-                    serializer.endTag(null, TAG_PRINTER);
-
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "[PERSISTED] " + printer);
-                    }
-                }
-
-                serializer.endTag(null, TAG_PRINTERS);
-                serializer.endDocument();
-                mStatePersistFile.finishWrite(out);
-
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "[PERSIST END]");
-                }
-            } catch (IOException ioe) {
-                Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
-                mStatePersistFile.failWrite(out);
-            } finally {
-                IoUtils.closeQuietly(out);
-            }
-        }
-
-        public void readStateLocked() {
-            FileInputStream in = null;
-            try {
-                in = mStatePersistFile.openRead();
-            } catch (FileNotFoundException e) {
-                Log.i(LOG_TAG, "No existing printer history.");
-                return;
-            }
-            try {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(in, null);
-                parseState(parser);
-            } catch (IllegalStateException ise) {
-                Slog.w(LOG_TAG, "Failed parsing ", ise);
-            } catch (NullPointerException npe) {
-                Slog.w(LOG_TAG, "Failed parsing ", npe);
-            } catch (NumberFormatException nfe) {
-                Slog.w(LOG_TAG, "Failed parsing ", nfe);
-            } catch (XmlPullParserException xppe) {
-                Slog.w(LOG_TAG, "Failed parsing ", xppe);
-            } catch (IOException ioe) {
-                Slog.w(LOG_TAG, "Failed parsing ", ioe);
-            } catch (IndexOutOfBoundsException iobe) {
-                Slog.w(LOG_TAG, "Failed parsing ", iobe);
-            } finally {
-                IoUtils.closeQuietly(in);
-            }
-            notifyChanged();
-        }
-
-        private void parseState(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            parser.next();
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
-            parser.next();
-
-            while (parsePrinter(parser)) {
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
-
-            // We were reading the new records first and appended them first,
-            // hence the historical list is in a reversed order, so fix that.
-            Collections.reverse(mHistoricalPrinters);
-        }
-
-        private boolean parsePrinter(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            skipEmptyTextTags(parser);
-            if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
-                return false;
-            }
-
-            String name = parser.getAttributeValue(null, ATTR_NAME);
-            String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
-            final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
-
-            parser.next();
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
-            String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
-            ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
-                    null, ATTR_SERVICE_NAME));
-            PrinterId printerId =  new PrinterId(service, localId);
-            parser.next();
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
-            parser.next();
-
-            PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
-            builder.setDescription(description);
-            PrinterInfo printer = builder.create();
-
-            addPrinterInternal(printer);
-
-            if (DEBUG) {
-                Log.i(LOG_TAG, "[RESTORED] " + printer);
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
-
-            return true;
-        }
-
-        private void expect(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (!accept(parser, type, tag)) {
-                throw new XmlPullParserException("Exepected event: " + type
-                        + " and tag: " + tag + " but got event: " + parser.getEventType()
-                        + " and tag:" + parser.getName());
-            }
-        }
-
-        private void skipEmptyTextTags(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            while (accept(parser, XmlPullParser.TEXT, null)
-                    && "\n".equals(parser.getText())) {
-                parser.next();
-            }
-        }
-
-        private boolean accept(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (parser.getEventType() != type) {
-                return false;
-            }
-            if (tag != null) {
-                if (!tag.equals(parser.getName())) {
-                    return false;
-                }
-            } else if (parser.getName() != null) {
-                return false;
-            }
-            return true;
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
new file mode 100644
index 0000000..6bad5b3
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -0,0 +1,575 @@
+/*
+ * 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 com.android.printspooler;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Loader;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.PrintSpoolerService.PrinterDiscoverySession;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is responsible for loading printers by doing discovery
+ * and merging the discovered printers with the previously used ones.
+ */
+public class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+    private static final String LOG_TAG = "FusedPrintersProvider";
+
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
+
+    private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
+
+    private static final int MAX_HISTORY_LENGTH = 50;
+
+    private static final int MAX_HISTORICAL_PRINTER_COUNT = 4;
+
+    private final Map<PrinterId, PrinterInfo> mPrinters =
+            new LinkedHashMap<PrinterId, PrinterInfo>();
+
+    private final PersistenceManager mPersistenceManager;
+
+    private PrinterDiscoverySession mDiscoverySession;
+
+    private List<PrinterInfo> mFavoritePrinters;
+
+    public FusedPrintersProvider(Context context) {
+        super(context);
+        mPersistenceManager = new PersistenceManager(context);
+    }
+
+    public void addHistoricalPrinter(PrinterInfo printer) {
+        mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
+    }
+
+    public List<PrinterInfo> getPrinters() {
+        return new ArrayList<PrinterInfo>(mPrinters.values());
+    }
+
+    @Override
+    public void deliverResult(List<PrinterInfo> printers) {
+        if (isStarted()) {
+            super.deliverResult(printers);
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onStartLoading()");
+        }
+        // The contract is that if we already have a valid,
+        // result the we have to deliver it immediately.
+        if (!mPrinters.isEmpty()) {
+            deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+        }
+        // If the data has changed since the last load
+        // or is not available, start a load.
+        if (takeContentChanged() || mPrinters.isEmpty()) {
+            onForceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onStopLoading()");
+        }
+        onCancelLoad();
+    }
+
+    @Override
+    protected void onForceLoad() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onForceLoad()");
+        }
+        onCancelLoad();
+        loadInternal();
+    }
+
+    private void loadInternal() {
+        if (mDiscoverySession == null) {
+            mDiscoverySession = new MyPrinterDiscoverySession();
+            mPersistenceManager.readPrinterHistory();
+        }
+        if (mPersistenceManager.isReadHistoryCompleted()
+                && !mDiscoverySession.isStarted()) {
+            final int favoriteCount = Math.min(MAX_HISTORICAL_PRINTER_COUNT,
+                    mFavoritePrinters.size());
+            List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+            for (int i = 0; i < favoriteCount; i++) {
+                printerIds.add(mFavoritePrinters.get(i).getId());
+            }
+            mDiscoverySession.startPrinterDisovery(printerIds);
+        }
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onCancelLoad()");
+        }
+        return cancelInternal();
+    }
+
+    private boolean cancelInternal() {
+        if (mDiscoverySession != null && mDiscoverySession.isStarted()) {
+            mDiscoverySession.stopPrinterDiscovery();
+            return true;
+        } else if (mPersistenceManager.isReadHistoryInProgress()) {
+            return mPersistenceManager.stopReadPrinterHistory();
+        }
+        return false;
+    }
+
+    @Override
+    protected void onReset() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onReset()");
+        }
+        onStopLoading();
+        mPrinters.clear();
+        if (mDiscoverySession != null) {
+            mDiscoverySession.destroy();
+            mDiscoverySession = null;
+        }
+    }
+
+    @Override
+    protected void onAbandon() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onAbandon()");
+        }
+        onStopLoading();
+    }
+
+    public void refreshPrinter(PrinterId printerId) {
+        if (isStarted() && mDiscoverySession != null && mDiscoverySession.isStarted()) {
+            mDiscoverySession.requestPrinterUpdated(printerId);
+        }
+    }
+
+    private final class MyPrinterDiscoverySession extends PrinterDiscoverySession {
+
+        @Override
+        public void onPrintersAdded(List<PrinterInfo> printers) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersAdded()");
+            }
+            boolean printersAdded = false;
+            final int addedPrinterCount = printers.size();
+            for (int i = 0; i < addedPrinterCount; i++) {
+                PrinterInfo printer = printers.get(i);
+                if (!mPrinters.containsKey(printer.getId())) {
+                    mPrinters.put(printer.getId(), printer);
+                    printersAdded = true;
+                }
+            }
+            if (printersAdded) {
+                deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+            }
+        }
+
+        @Override
+        public void onPrintersRemoved(List<PrinterId> printerIds) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersRemoved()");
+            }
+            boolean removedPrinters = false;
+            final int removedPrinterCount = printerIds.size();
+            for (int i = 0; i < removedPrinterCount; i++) {
+                PrinterId removedPrinterId = printerIds.get(i);
+                if (mPrinters.remove(removedPrinterId) != null) {
+                    removedPrinters = true;
+                }
+            }
+            if (removedPrinters) {
+                deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+            }
+        }
+
+        @Override
+        public void onPrintersUpdated(List<PrinterInfo> printers) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersUpdated()");
+            }
+            boolean updatedPrinters = false;
+            final int updatedPrinterCount = printers.size();
+            for (int i = 0; i < updatedPrinterCount; i++) {
+                PrinterInfo updatedPrinter = printers.get(i);
+                if (mPrinters.containsKey(updatedPrinter.getId())) {
+                    mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+                    updatedPrinters = true;
+                }
+            }
+            if (updatedPrinters) {
+                deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+            }
+        }
+    }
+
+    private final class PersistenceManager {
+        private static final String PERSIST_FILE_NAME = "printer_history.xml";
+
+        private static final String TAG_PRINTERS = "printers";
+
+        private static final String TAG_PRINTER = "printer";
+        private static final String TAG_PRINTER_ID = "printerId";
+
+        private static final String ATTR_LOCAL_ID = "localId";
+        private static final String ATTR_SERVICE_NAME = "serviceName";
+
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_DESCRIPTION = "description";
+        private static final String ATTR_STATUS = "status";
+
+        private final AtomicFile mStatePersistFile;
+
+        private List<PrinterInfo> mHistoricalPrinters;
+
+        private boolean mReadHistoryCompleted;
+        private boolean mReadHistoryInProgress;
+
+        private final AsyncTask<Void, Void, List<PrinterInfo>> mReadTask =
+                new AsyncTask<Void, Void, List<PrinterInfo>>() {
+            @Override
+            protected List<PrinterInfo> doInBackground(Void... args) {
+               return doReadPrinterHistory();
+            }
+
+            @Override
+            protected void onPostExecute(List<PrinterInfo> printers) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "read history completed");
+                }
+
+                mHistoricalPrinters = printers;
+
+                // Compute the favorite printers.
+                mFavoritePrinters = computeFavoritePrinters(printers);
+
+                // We want the first few favorite printers on top of the list.
+                final int favoriteCount = Math.min(mFavoritePrinters.size(),
+                        MAX_HISTORICAL_PRINTER_COUNT);
+                for (int i = 0; i < favoriteCount; i++) {
+                    PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+                    mPrinters.put(favoritePrinter.getId(), favoritePrinter);
+                }
+
+                mReadHistoryInProgress = false;
+                mReadHistoryCompleted = true;
+
+                loadInternal();
+            }
+
+            private List<PrinterInfo> doReadPrinterHistory() {
+                FileInputStream in = null;
+                try {
+                    in = mStatePersistFile.openRead();
+                } catch (FileNotFoundException fnfe) {
+                    Log.i(LOG_TAG, "No existing printer history.");
+                    return new ArrayList<PrinterInfo>();
+                }
+                try {
+                    List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+                    XmlPullParser parser = Xml.newPullParser();
+                    parser.setInput(in, null);
+                    parseState(parser, printers);
+                    return printers;
+                } catch (IllegalStateException ise) {
+                    Slog.w(LOG_TAG, "Failed parsing ", ise);
+                } catch (NullPointerException npe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", npe);
+                } catch (NumberFormatException nfe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", nfe);
+                } catch (XmlPullParserException xppe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", xppe);
+                } catch (IOException ioe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", ioe);
+                } catch (IndexOutOfBoundsException iobe) {
+                    Slog.w(LOG_TAG, "Failed parsing ", iobe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                }
+
+                return Collections.emptyList();
+            }
+
+            private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
+                    throws IOException, XmlPullParserException {
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
+                parser.next();
+
+                while (parsePrinter(parser, outPrinters)) {
+                    // Be nice and respond to cancellation
+                    if (isCancelled()) {
+                        return;
+                    }
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
+            }
+
+            private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
+                    throws IOException, XmlPullParserException {
+                skipEmptyTextTags(parser);
+                if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
+                    return false;
+                }
+
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+                final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
+
+                parser.next();
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
+                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+                        null, ATTR_SERVICE_NAME));
+                PrinterId printerId =  new PrinterId(service, localId);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+                parser.next();
+
+                PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+                builder.setDescription(description);
+                PrinterInfo printer = builder.create();
+
+                outPrinters.add(printer);
+
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "[RESTORED] " + printer);
+                }
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+
+                return true;
+            }
+
+            private void expect(XmlPullParser parser, int type, String tag)
+                    throws IOException, XmlPullParserException {
+                if (!accept(parser, type, tag)) {
+                    throw new XmlPullParserException("Exepected event: " + type
+                            + " and tag: " + tag + " but got event: " + parser.getEventType()
+                            + " and tag:" + parser.getName());
+                }
+            }
+
+            private void skipEmptyTextTags(XmlPullParser parser)
+                    throws IOException, XmlPullParserException {
+                while (accept(parser, XmlPullParser.TEXT, null)
+                        && "\n".equals(parser.getText())) {
+                    parser.next();
+                }
+            }
+
+            private boolean accept(XmlPullParser parser, int type, String tag)
+                    throws IOException, XmlPullParserException {
+                if (parser.getEventType() != type) {
+                    return false;
+                }
+                if (tag != null) {
+                    if (!tag.equals(parser.getName())) {
+                        return false;
+                    }
+                } else if (parser.getName() != null) {
+                    return false;
+                }
+                return true;
+            }
+        };
+
+        private final AsyncTask<List<PrinterInfo>, Void, Void> mWriteTask =
+                new AsyncTask<List<PrinterInfo>, Void, Void>() {
+            @Override
+            protected Void doInBackground(List<PrinterInfo>... printers) {
+                doWritePrinterHistory(printers[0]);
+                return null;
+            }
+
+            private void doWritePrinterHistory(List<PrinterInfo> printers) {
+                FileOutputStream out = null;
+                try {
+                    out = mStatePersistFile.startWrite();
+
+                    XmlSerializer serializer = new FastXmlSerializer();
+                    serializer.setOutput(out, "utf-8");
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, TAG_PRINTERS);
+
+                    final int printerCount = printers.size();
+                    for (int i = 0; i < printerCount; i++) {
+                        PrinterInfo printer = printers.get(i);
+
+                        serializer.startTag(null, TAG_PRINTER);
+
+                        serializer.attribute(null, ATTR_NAME, printer.getName());
+                        serializer.attribute(null, ATTR_STATUS, String.valueOf(
+                                printer.getStatus()));
+                        String description = printer.getDescription();
+                        if (description != null) {
+                            serializer.attribute(null, ATTR_DESCRIPTION, description);
+                        }
+
+                        PrinterId printerId = printer.getId();
+                        serializer.startTag(null, TAG_PRINTER_ID);
+                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+                                .flattenToString());
+                        serializer.endTag(null, TAG_PRINTER_ID);
+
+                        serializer.endTag(null, TAG_PRINTER);
+
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "[PERSISTED] " + printer);
+                        }
+                    }
+
+                    serializer.endTag(null, TAG_PRINTERS);
+                    serializer.endDocument();
+                    mStatePersistFile.finishWrite(out);
+
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "[PERSIST END]");
+                    }
+                } catch (IOException ioe) {
+                    Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
+                    mStatePersistFile.failWrite(out);
+                } finally {
+                    IoUtils.closeQuietly(out);
+                }
+            }
+        };
+
+        private PersistenceManager(Context context) {
+            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+                    PERSIST_FILE_NAME));
+        }
+
+        public boolean isReadHistoryInProgress() {
+            return mReadHistoryInProgress;
+        }
+
+        public boolean isReadHistoryCompleted() {
+            return mReadHistoryCompleted;
+        }
+
+        public boolean stopReadPrinterHistory() {
+            return mReadTask.cancel(true);
+        }
+
+        public void readPrinterHistory() {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "read history started");
+            }
+            mReadHistoryInProgress = true;
+            mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        }
+
+        @SuppressWarnings("unchecked")
+        public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
+            if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
+                mHistoricalPrinters.remove(0);
+            }
+            mHistoricalPrinters.add(printer);
+            mWriteTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mHistoricalPrinters);
+        }
+
+        private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
+            Map<PrinterId, PrinterRecord> recordMap =
+                    new ArrayMap<PrinterId, PrinterRecord>();
+
+            // Recompute the weights.
+            float currentWeight = 1.0f;
+            final int printerCount = printers.size();
+            for (int i = printerCount - 1; i >= 0; i--) {
+                PrinterInfo printer = printers.get(i);
+                // Aggregate weight for the same printer
+                PrinterRecord record = recordMap.get(printer.getId());
+                if (record == null) {
+                    record = new PrinterRecord(printer);
+                    recordMap.put(printer.getId(), record);
+                }
+                record.weight += currentWeight;
+                currentWeight *= WEIGHT_DECAY_COEFFICIENT;
+            }
+
+            // Soft the favorite printers.
+            List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+                    recordMap.values());
+            Collections.sort(favoriteRecords);
+
+            // Write the favorites to the output.
+            final int favoriteCount = favoriteRecords.size();
+            List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+            for (int i = 0; i < favoriteCount; i++) {
+                PrinterInfo printer = favoriteRecords.get(i).printer;
+                favoritePrinters.add(printer);
+            }
+
+            return favoritePrinters;
+        }
+
+        private final class PrinterRecord implements Comparable<PrinterRecord> {
+            public final PrinterInfo printer;
+            public float weight;
+
+            public PrinterRecord(PrinterInfo printer) {
+                this.printer = printer;
+            }
+
+            @Override
+            public int compareTo(PrinterRecord another) {
+                return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index f8e9f43..d3dd8c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -18,13 +18,17 @@
 
 import android.app.Activity;
 import android.app.Dialog;
+import android.app.LoaderManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.DataSetObserver;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -39,9 +43,11 @@
 import android.print.PageRange;
 import android.print.PrintAttributes;
 import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintDocumentInfo;
 import android.print.PrintJobInfo;
+import android.print.PrintManager;
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
@@ -69,6 +75,14 @@
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -93,15 +107,32 @@
     public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
     public static final String EXTRA_PRINT_JOB_ID = "printJobId";
 
-    private static final int CONTROLLER_STATE_INITIALIZED = 1;
-    private static final int CONTROLLER_STATE_STARTED = 2;
-    private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
-    private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
-    private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
-    private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
-    private static final int CONTROLLER_STATE_FINISHED = 7;
-    private static final int CONTROLLER_STATE_FAILED = 8;
-    private static final int CONTROLLER_STATE_CANCELLED = 9;
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+    private static final int DEST_ADAPTER_MIN_ITEM_COUNT = 2;
+    private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
+
+    private static final int DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS = 0;
+    private static final int DEST_ADAPTER_POSITION_SAVE_AS_PDF = 1;
+
+    private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
+    private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+    private static final int DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS = Integer.MAX_VALUE - 2;
+
+    private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
+    private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
+
+    private static final int CONTROLLER_STATE_FINISHED = 1;
+    private static final int CONTROLLER_STATE_FAILED = 2;
+    private static final int CONTROLLER_STATE_CANCELLED = 3;
+    private static final int CONTROLLER_STATE_INITIALIZED = 4;
+    private static final int CONTROLLER_STATE_STARTED = 5;
+    private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
+    private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
+    private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
+    private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
 
     private static final int EDITOR_STATE_INITIALIZED = 1;
     private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
@@ -109,6 +140,7 @@
     private static final int EDITOR_STATE_CANCELLED = 4;
 
     private static final int MIN_COPIES = 1;
+    private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
 
     private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
 
@@ -135,10 +167,6 @@
     private Document mDocument;
     private PrintController mController;
 
-    private AvailablePrinterProvider mAvailablePrinters;
-
-    private FavoritePrinterProvider mFavoritePrinters;
-
     private int mPrintJobId;
 
     private IBinder mIPrintDocumentAdapter;
@@ -148,9 +176,6 @@
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
-        setContentView(R.layout.print_job_config_activity_container);
-
-        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
 
         Bundle extras = getIntent().getExtras();
 
@@ -169,15 +194,17 @@
             mCurrPrintAttributes.copyFrom(attributes);
         }
 
-        // TODO: Use history
-        mAvailablePrinters = new AvailablePrinterProvider(this, null);
-        mFavoritePrinters = new FavoritePrinterProvider(this);
+        setContentView(R.layout.print_job_config_activity_container);
+
+        // TODO: This should be on the style
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
 
         mEditor = new Editor();
         mDocument = new Document();
         mController = new PrintController(new RemotePrintDocumentAdapter(
                 IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                PrintSpooler.peekInstance().generateFileForPrintJob(mPrintJobId)));
+                PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
 
         try {
             mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
@@ -191,26 +218,6 @@
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
-        // TODO: Polish this
-        if (!mEditor.isPrintConfirmed()) {
-            mAvailablePrinters.startLoadData();
-            mFavoritePrinters.startLoadData();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        // TODO: Polish this
-        if (!mEditor.isPrintConfirmed()) {
-            mAvailablePrinters.stopLoadData();
-            mFavoritePrinters.stopLoadData();
-        }
-        super.onPause();
-    }
-
-    @Override
     protected void onDestroy() {
         // We can safely do the work in here since at this point
         // the system is bound to our (spooler) process which
@@ -219,10 +226,10 @@
             mController.finish();
         }
         if (mEditor.isPrintConfirmed() && mController.isFinished()) {
-            PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+            PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_QUEUED, null);
         } else {
-            PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+            PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_CANCELED, null);
         }
         mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
@@ -333,13 +340,13 @@
 
         public void update() {
             if (!printAttributesChanged()) {
-                // If the attributes changes, then we do not do a layout but may
+                // If the attributes changed, then we do not do a layout but may
                 // have to ask the app to write some pages. Hence, pretend layout
                 // completed and nothing changed, so we handle writing as usual.
                 handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
             } else {
-                PrintSpooler.peekInstance().setPrintJobAttributesNoPersistence(mPrintJobId,
-                        mCurrPrintAttributes);
+                PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence(
+                        mPrintJobId, mCurrPrintAttributes);
 
                 mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
                         !mEditor.isPrintConfirmed());
@@ -378,7 +385,7 @@
             final boolean infoChanged = !info.equals(mDocument.info);
             if (infoChanged) {
                 mDocument.info = info;
-                PrintSpooler.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+                PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
                         mPrintJobId, info);
             }
 
@@ -465,12 +472,12 @@
             if (Arrays.equals(mDocument.pages, mRequestedPages)) {
                 // We got a document with exactly the pages we wanted. Hence,
                 // the printer has to print all pages in the data.
-                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
                         ALL_PAGES_ARRAY);
             } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
                 // We requested specific pages but got all of them. Hence,
                 // the printer has to print only the requested pages.
-                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
                         mRequestedPages);
             } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
                 // We requested specific pages and got more but not all pages.
@@ -480,7 +487,7 @@
                 final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
                 PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
                 PageRangeUtils.offsetStart(offsetPages, offset);
-                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
                         offsetPages);
             } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
                     && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
@@ -488,7 +495,7 @@
                 // We requested all pages via the special constant and got all
                 // of them as an explicit enumeration. Hence, the printer has
                 // to print only the requested pages.
-                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
                         mDocument.pages);
             } else {
                 // We did not get the pages we requested, then the application
@@ -500,7 +507,16 @@
             }
 
             if (mEditor.isDone()) {
-                PrintJobConfigActivity.this.finish();
+                if (mEditor.isPrintingToPdf()) {
+                    PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+                            .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+                    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+                    intent.setType("application/pdf");
+                    intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel());
+                    startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+                } else {
+                    PrintJobConfigActivity.this.finish();
+                }
             }
         }
 
@@ -607,33 +623,111 @@
         }
     }
 
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case ACTIVITY_REQUEST_CREATE_FILE: {
+                if (data != null) {
+                    Uri uri = data.getData();
+                    writePrintJobDataAndFinish(uri);
+                } else {
+                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
+                            new Runnable() {
+                        @Override
+                        public void run() {
+                            mEditor.initialize();
+                            mEditor.bindUi();
+                            mEditor.updateUi();
+                        }
+                    });
+                }
+            } break;
+
+            case ACTIVITY_REQUEST_SELECT_PRINTER: {
+                if (resultCode == RESULT_OK) {
+                    PrinterId printerId = (PrinterId) data.getParcelableExtra(
+                            INTENT_EXTRA_PRINTER_ID);
+                    // TODO: Make sure the selected printer is in the shown list.
+                    mEditor.selectPrinter(printerId);
+                }
+            } break;
+        }
+    }
+
+    private void writePrintJobDataAndFinish(final Uri uri) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                InputStream in = null;
+                OutputStream out = null;
+                try {
+                    PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+                            .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+                    if (printJob == null) {
+                        return null;
+                    }
+                    File file = PrintSpoolerService.peekInstance()
+                            .generateFileForPrintJob(mPrintJobId);
+                    in = new FileInputStream(file);
+                    out = getContentResolver().openOutputStream(uri);
+                    final byte[] buffer = new byte[8192];
+                    while (true) {
+                        final int readByteCount = in.read(buffer);
+                        if (readByteCount < 0) {
+                            break;
+                        }
+                        out.write(buffer, 0, readByteCount);
+                    }
+                } catch (FileNotFoundException fnfe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                    IoUtils.closeQuietly(out);
+                }
+                return null;
+            }
+
+            @Override
+            public void onPostExecute(Void result) {
+                mEditor.cancel();
+                PrintJobConfigActivity.this.finish();
+            }
+        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+    }
+
     private final class Editor {
-        private final EditText mCopiesEditText;
+        private static final int UI_NONE = 0;
+        private static final int UI_EDITING_PRINT_JOB = 1;
+        private static final int UI_GENERATING_PRINT_JOB = 2;
 
-        private final TextView mRangeTitle;
-        private final EditText mRangeEditText;
+        private EditText mCopiesEditText;
 
-        private final Spinner mDestinationSpinner;
+        private TextView mRangeTitle;
+        private EditText mRangeEditText;
+
+        private Spinner mDestinationSpinner;
         private final DestinationAdapter mDestinationSpinnerAdapter;
 
-        private final Spinner mMediaSizeSpinner;
+        private Spinner mMediaSizeSpinner;
         private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
 
-        private final Spinner mColorModeSpinner;
+        private Spinner mColorModeSpinner;
         private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
 
-        private final Spinner mOrientationSpinner;
+        private Spinner mOrientationSpinner;
         private final  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
 
-        private final Spinner mRangeOptionsSpinner;
+        private Spinner mRangeOptionsSpinner;
         private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
 
         private final SimpleStringSplitter mStringCommaSplitter =
                 new SimpleStringSplitter(',');
 
-        private final View mContentContainer;
+        private View mContentContainer;
 
-        private final Button mPrintButton;
+        private Button mPrintButton;
 
         private final OnItemSelectedListener mOnItemSelectedListener =
                 new AdapterView.OnItemSelectedListener() {
@@ -644,18 +738,31 @@
                         mIgnoreNextDestinationChange = false;
                         return;
                     }
+                    if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+                        mIgnoreNextDestinationChange = true;
+                        mDestinationSpinner.setSelection(0);
+                        Intent intent = new Intent(PrintJobConfigActivity.this,
+                                SelectPrinterActivity.class);
+                        startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
+                        return;
+                    }
+                    mWaitingForPrinterCapabilities = false;
                     mCurrPrintAttributes.clear();
                     PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter
                             .getItem(position);
                     if (printer != null) {
-                        PrintSpooler.peekInstance().setPrintJobPrinterNoPersistence(
+                        PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
                                 mPrintJobId, printer);
                         PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
                         if (capabilities == null) {
                             List<PrinterId> printerIds = new ArrayList<PrinterId>();
                             printerIds.add(printer.getId());
-                            final int index = mAvailablePrinters.getItemIndex(printer);
-                            mAvailablePrinters.refreshItem(index);
+                            FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+                                    (Loader<?>) getLoaderManager().getLoader(
+                                            LOADER_ID_PRINTERS_LOADER);
+                            if (printersLoader != null) {
+                                printersLoader.refreshPrinter(printer.getId());
+                            }
                             mWaitingForPrinterCapabilities = true;
                             //TODO: We need a timeout for the update.
                         } else {
@@ -668,6 +775,31 @@
                             }
                         }
                     }
+
+                    // The printer changed so we want to start with a clean slate
+                    // for the print options and let them be populated from the
+                    // printer capabilities and use the printer defaults.
+                    if (!mMediaSizeSpinnerAdapter.isEmpty()) {
+                        mIgnoreNextMediaSizeChange = true;
+                        mMediaSizeSpinnerAdapter.clear();
+                    }
+                    if (!mColorModeSpinnerAdapter.isEmpty()) {
+                        mIgnoreNextColorModeChange = true;
+                        mColorModeSpinnerAdapter.clear();
+                    }
+                    if (!mOrientationSpinnerAdapter.isEmpty()) {
+                        mIgnoreNextOrientationChange = true;
+                        mOrientationSpinnerAdapter.clear();
+                    }
+                    if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                        mIgnoreNextRangeOptionChange = true;
+                        mRangeOptionsSpinner.setSelection(0);
+                    }
+                    if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
+                        mIgnoreNextCopiesChange = true;
+                        mCopiesEditText.setText(MIN_COPIES_STRING);
+                    }
+
                     updateUi();
                 } else if (spinner == mMediaSizeSpinner) {
                     if (mIgnoreNextMediaSizeChange) {
@@ -753,7 +885,8 @@
                 }
 
                 mCopiesEditText.setError(null);
-                PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+                PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+                        mPrintJobId, copies);
                 updateUi();
 
                 if (hadErrors && !hasErrors() && printAttributesChanged()) {
@@ -832,22 +965,25 @@
 
         private boolean mWaitingForPrinterCapabilities;
 
+        private int mCurrentUi = UI_NONE;
+
         public Editor() {
-            // Content container
-            mContentContainer = findViewById(R.id.content_container);
-
-            // Copies
-            mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
-            mCopiesEditText.setText(String.valueOf(MIN_COPIES));
-            PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
-            mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
-            mCopiesEditText.selectAll();
-
             // Destination.
-            mDestinationSpinnerAdapter = new DestinationAdapter(mAvailablePrinters);
+            mDestinationSpinnerAdapter = new DestinationAdapter();
             mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
                 @Override
                 public void onChanged() {
+                    final int selectedPosition = mDestinationSpinner.getSelectedItemPosition();
+                    if (mDestinationSpinnerAdapter.getCount() > 0) {
+                        // Make sure we select the first printer if we have data.
+                        if (selectedPosition == AdapterView.INVALID_POSITION) {
+                            mDestinationSpinner.setSelection(0);
+                        }
+                    } else {
+                        // Make sure we select no printer if we have no data.
+                        mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+                    }
+
                     // Maybe we did not have capabilities when the current printer was
                     // selected, but now the selected printer has capabilities. Generate
                     // a fake selection so the code in the selection change handling takes
@@ -855,13 +991,9 @@
                     if (mWaitingForPrinterCapabilities) {
                         mWaitingForPrinterCapabilities = false;
                         PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-                        if (printer != null) {
-                            if (printer.getCapabilities() != null) {
-                                final int selectedPosition =
-                                        mDestinationSpinner.getSelectedItemPosition();
-                                mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
-                                        selectedPosition, selectedPosition);
-                            }
+                        if (printer != null && printer.getCapabilities() != null) {
+                            mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
+                                    selectedPosition, selectedPosition);
                         }
                     }
                     updateUi();
@@ -872,41 +1004,23 @@
                     updateUi();
                 }
             });
-            mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
-            mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
-            mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
             // Media size.
-            mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
             mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
                     PrintJobConfigActivity.this,
                     R.layout.spinner_dropdown_item, R.id.title);
-            mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
-            mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
             // Color mode.
-            mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
             mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
                     PrintJobConfigActivity.this,
                     R.layout.spinner_dropdown_item, R.id.title);
-            mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
-            mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
             // Orientation
-            mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
             mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
                     PrintJobConfigActivity.this,
                     R.layout.spinner_dropdown_item, R.id.title);
-            mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
-            mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-
-            // Range
-            mRangeTitle = (TextView) findViewById(R.id.page_range_title);
-            mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
-            mRangeEditText.addTextChangedListener(mRangeTextWatcher);
 
             // Range options
-            mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
             mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
                     PrintJobConfigActivity.this,
                     R.layout.spinner_dropdown_item, R.id.title);
@@ -919,26 +1033,28 @@
                 mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
                         rangeOptionsValues[i], rangeOptionsLabels[i]));
             }
-            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
-            if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
-                mIgnoreNextRangeOptionChange = true;
-                mRangeOptionsSpinner.setSelection(0);
-            }
 
-            // Print button
-            mPrintButton = (Button) findViewById(R.id.print_button);
-            mPrintButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    mEditor.confirmPrint();
-                    mController.update();
-                    showGeneratingPrintJobUi();
-                }
-            });
-
+            showUi(UI_EDITING_PRINT_JOB, null);
+            bindUi();
             updateUi();
         }
 
+        public void selectPrinter(PrinterId printerId) {
+            final int printerCount = mDestinationSpinnerAdapter.getCount();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter.getItem(i);
+                if (printer.getId().equals(printerId)) {
+                    mDestinationSpinner.setSelection(i);
+                    return;
+                }
+            }
+        }
+
+        public boolean isPrintingToPdf() {
+            return mDestinationSpinner.getSelectedItem()
+                    == mDestinationSpinnerAdapter.mFakePdfPrinter;
+        }
+
         public boolean shouldCloseOnTouch(MotionEvent event) {
             if (event.getAction() != MotionEvent.ACTION_DOWN) {
                 return false;
@@ -966,19 +1082,104 @@
         }
 
         public boolean isShwoingGeneratingPrintJobUi() {
-            return (findViewById(R.id.content_generating) != null);
+            return (mCurrentUi == UI_GENERATING_PRINT_JOB);
         }
 
-        private void showGeneratingPrintJobUi() {
-            // Find everything we will shuffle around.
-            final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
-            final View contentEditing = contentContainer.findViewById(R.id.content_editing);
-            final View contentGenerating = getLayoutInflater().inflate(
-                    R.layout.print_job_config_activity_content_generating,
-                    contentContainer, false);
+        public void showUi(int ui, final Runnable postSwitchCallback) {
+            if (ui == UI_NONE) {
+                throw new IllegalStateException("cannot remove the ui");
+            }
 
-            // Wire the cancel action.
-            Button cancelButton = (Button) contentGenerating.findViewById(R.id.cancel_button);
+            if (mCurrentUi == ui) {
+                return;
+            }
+
+            switch (mCurrentUi) {
+                case UI_NONE: {
+                    switch (ui) {
+                        case UI_EDITING_PRINT_JOB: {
+                            doUiSwitch(R.layout.print_job_config_activity_content_editing);
+                            registerPrintButtonClickListener();
+                            if (postSwitchCallback != null) {
+                                postSwitchCallback.run();
+                            }
+                        } break;
+
+                        case UI_GENERATING_PRINT_JOB: {
+                            doUiSwitch(R.layout.print_job_config_activity_content_generating);
+                            registerCancelButtonClickListener();
+                            if (postSwitchCallback != null) {
+                                postSwitchCallback.run();
+                            }
+                        } break;
+                    }
+                } break;
+
+                case UI_EDITING_PRINT_JOB: {
+                    switch (ui) {
+                        case UI_GENERATING_PRINT_JOB: {
+                            animateUiSwitch(R.layout.print_job_config_activity_content_generating,
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    registerCancelButtonClickListener();
+                                    if (postSwitchCallback != null) {
+                                        postSwitchCallback.run();
+                                    }
+                                }
+                            });
+                        } break;
+                    }
+                } break;
+
+                case UI_GENERATING_PRINT_JOB: {
+                    switch (ui) {
+                        case UI_EDITING_PRINT_JOB: {
+                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    registerPrintButtonClickListener();
+                                    if (postSwitchCallback != null) {
+                                        postSwitchCallback.run();
+                                    }
+                                }
+                            });
+                        } break;
+                    }
+                } break;
+            }
+
+            mCurrentUi = ui;
+        }
+
+        private void registerPrintButtonClickListener() {
+            Button printButton = (Button) findViewById(R.id.print_button);
+            printButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+                    if (printer != null) {
+                        mEditor.confirmPrint();
+                        mController.update();
+                        if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
+                            FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+                                    (Loader<?>) getLoaderManager().getLoader(
+                                            LOADER_ID_PRINTERS_LOADER);
+                            if (printersLoader != null) {
+                                printersLoader.addHistoricalPrinter(printer);
+                            }
+                        }
+                    } else {
+                        mEditor.cancel();
+                        PrintJobConfigActivity.this.finish();
+                    }
+                }
+            });
+        }
+
+        private void registerCancelButtonClickListener() {
+            Button cancelButton = (Button) findViewById(R.id.cancel_button);
             cancelButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -988,24 +1189,38 @@
                     mEditor.cancel();
                 }
             });
+        }
+
+        private void doUiSwitch(int showLayoutId) {
+            ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+            contentContainer.removeAllViews();
+            getLayoutInflater().inflate(showLayoutId, contentContainer, true);
+        }
+
+        private void animateUiSwitch(int showLayoutId, final Runnable postAnimateCommand) {
+            // Find everything we will shuffle around.
+            final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+            final View hidingView = contentContainer.getChildAt(0);
+            final View showingView = getLayoutInflater().inflate(showLayoutId,
+                    null, false);
 
             // First animation - fade out the old content.
-            contentEditing.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
+            hidingView.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
                 @Override
                 public void run() {
-                    contentEditing.setVisibility(View.INVISIBLE);
+                    hidingView.setVisibility(View.INVISIBLE);
 
                     // Prepare the new content with correct size and alpha.
-                    contentGenerating.setMinimumWidth(contentContainer.getWidth());
-                    contentGenerating.setAlpha(0.0f);
+                    showingView.setMinimumWidth(contentContainer.getWidth());
+                    showingView.setAlpha(0.0f);
 
-                    // Compute how to much shrink the container to fit around the new content.
+                    // Compute how to much shrink /stretch the content.
                     final int widthSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getWidth(), MeasureSpec.AT_MOST);
+                            contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
                     final int heightSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getHeight(), MeasureSpec.AT_MOST);
-                    contentGenerating.measure(widthSpec, heightSpec);
-                    final float scaleY = (float) contentGenerating.getMeasuredHeight()
+                            contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
+                    showingView.measure(widthSpec, heightSpec);
+                    final float scaleY = (float) showingView.getMeasuredHeight()
                             / (float) contentContainer.getHeight();
 
                     // Second animation - resize the container.
@@ -1016,10 +1231,16 @@
                             // Swap the old and the new content.
                             contentContainer.removeAllViews();
                             contentContainer.setScaleY(1.0f);
-                            contentContainer.addView(contentGenerating);
+                            contentContainer.addView(showingView);
 
                             // Third animation - show the new content.
-                            contentGenerating.animate().withLayer().alpha(1.0f);
+                            showingView.animate().withLayer().alpha(1.0f).withEndAction(
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    postAnimateCommand.run();
+                                }
+                            });
                         }
                     });
                 }
@@ -1028,10 +1249,6 @@
 
         public void initialize() {
             mEditorState = EDITOR_STATE_INITIALIZED;
-            if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
-                mIgnoreNextDestinationChange = true;
-                mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
-            }
         }
 
         public boolean isCancelled() {
@@ -1054,11 +1271,7 @@
 
         public void confirmPrint() {
             mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
-            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-            if (printer != null) {
-                mFavoritePrinters.addPrinter(printer);
-            }
-            updateUi();
+            showUi(UI_GENERATING_PRINT_JOB, null);
         }
 
         public boolean isPreviewConfirmed() {
@@ -1104,7 +1317,79 @@
             return ALL_PAGES_ARRAY;
         }
 
+        private void bindUi() {
+            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+                return;
+            }
+
+            // Content container
+            mContentContainer = findViewById(R.id.content_container);
+
+            // Copies
+            mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+            mCopiesEditText.setText(MIN_COPIES_STRING);
+            mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
+            mCopiesEditText.selectAll();
+            if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
+                mIgnoreNextCopiesChange = true;
+            }
+            PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+                    mPrintJobId, MIN_COPIES);
+
+            // Destination.
+            mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+            mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+            mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+            if (mDestinationSpinnerAdapter.getCount() > 0) {
+                mIgnoreNextDestinationChange = true;
+            }
+
+            // Media size.
+            mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
+            mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+            mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+            if (mMediaSizeSpinnerAdapter.getCount() > 0) {
+                mIgnoreNextMediaSizeChange = true;
+            }
+
+            // Color mode.
+            mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
+            mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+            mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+            if (mColorModeSpinnerAdapter.getCount() > 0) {
+                mIgnoreNextColorModeChange = true;
+            }
+
+            // Orientation
+            mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+            mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+            mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+            if (mOrientationSpinnerAdapter.getCount() > 0) {
+                mIgnoreNextOrientationChange = true;
+            }
+
+            // Range
+            mRangeTitle = (TextView) findViewById(R.id.page_range_title);
+            mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+            mRangeEditText.addTextChangedListener(mRangeTextWatcher);
+
+            // Range options
+            mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
+            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
+            mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+            if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
+                mIgnoreNextRangeOptionChange = true;
+            }
+
+            // Print button
+            mPrintButton = (Button) findViewById(R.id.print_button);
+            registerPrintButtonClickListener();
+        }
+
         public void updateUi() {
+            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+                return;
+            }
             if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
                 mDestinationSpinner.setEnabled(false);
                 mCopiesEditText.setEnabled(false);
@@ -1119,14 +1404,20 @@
                 return;
             }
 
+            // If a printer with capabilities is selected, then we enabled all options.
+            boolean allOptionsEnabled = false;
             final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+            if (selectedIndex >= 0) {
+                Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
+                if (item instanceof PrinterInfo) {
+                    PrinterInfo printer = (PrinterInfo) item;
+                    if (printer.getCapabilities() != null) {
+                        allOptionsEnabled = true;
+                    }
+                }
+            }
 
-            if (selectedIndex < 0 || ((PrinterInfo) mDestinationSpinnerAdapter.getItem(
-                    selectedIndex)).getCapabilities() == null) {
-
-                // Destination
-                mDestinationSpinner.setEnabled(false);
-
+            if (!allOptionsEnabled) {
                 String minCopiesString = String.valueOf(MIN_COPIES);
                 if (!TextUtils.equals(mCopiesEditText.getText(), minCopiesString)) {
                     mIgnoreNextCopiesChange = true;
@@ -1183,9 +1474,6 @@
                 PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
                 printer.getCapabilities().getDefaults(defaultAttributes);
 
-                // Destination
-                mDestinationSpinner.setEnabled(true);
-
                 // Copies
                 mCopiesEditText.setEnabled(true);
 
@@ -1223,6 +1511,7 @@
                         }
                     }
                 }
+                mMediaSizeSpinner.setEnabled(true);
 
                 // Color mode.
                 final int colorModes = capabilities.getColorModes();
@@ -1271,6 +1560,7 @@
                         }
                     }
                 }
+                mColorModeSpinner.setEnabled(true);
 
                 // Orientation.
                 final int orientations = capabilities.getOrientations();
@@ -1321,20 +1611,25 @@
                         }
                     }
                 }
+                mOrientationSpinner.setEnabled(true);
 
                 // Range options
                 PrintDocumentInfo info = mDocument.info;
                 if (info != null && (info.getPageCount() > 1
                         || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
                     mRangeOptionsSpinner.setEnabled(true);
-                    if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
-                            && !mRangeEditText.isEnabled()) {
-                        mRangeEditText.setEnabled(true);
-                        mRangeEditText.setVisibility(View.VISIBLE);
-                        mRangeEditText.requestFocus();
-                        InputMethodManager imm = (InputMethodManager)
-                                getSystemService(INPUT_METHOD_SERVICE);
-                        imm.showSoftInput(mRangeEditText, 0);
+                    if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+                        if (!mRangeEditText.isEnabled()) {
+                            mRangeEditText.setEnabled(true);
+                            mRangeEditText.setVisibility(View.VISIBLE);
+                            mRangeEditText.requestFocus();
+                            InputMethodManager imm = (InputMethodManager)
+                                    getSystemService(INPUT_METHOD_SERVICE);
+                            imm.showSoftInput(mRangeEditText, 0);
+                        }
+                    } else {
+                        mRangeEditText.setEnabled(false);
+                        mRangeEditText.setVisibility(View.INVISIBLE);
                     }
                     final int pageCount = mDocument.info.getPageCount();
                     mRangeTitle.setText(getString(R.string.label_pages,
@@ -1352,6 +1647,7 @@
                     mRangeEditText.setEnabled(false);
                     mRangeEditText.setVisibility(View.INVISIBLE);
                 }
+                mRangeOptionsSpinner.setEnabled(true);
 
                 // Print/Print preview
                 if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
@@ -1378,6 +1674,7 @@
                     mCopiesEditText.selectAll();
                     mCopiesEditText.requestFocus();
                 }
+                mCopiesEditText.setEnabled(true);
             }
         }
 
@@ -1407,38 +1704,51 @@
             }
         }
 
-        private final class DestinationAdapter extends BaseAdapter {
-            private final AvailablePrinterProvider mProvider;
+        private final class DestinationAdapter extends BaseAdapter
+                implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
+            private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
 
-            private final DataSetObserver mObserver = new DataSetObserver() {
-                @Override
-                public void onChanged() {
-                    notifyDataSetChanged();
-                }
+            public final PrinterInfo mFakePdfPrinter;
 
-                @Override
-                public void onInvalidated() {
-                    notifyDataSetInvalidated();
-                }
-            };
-
-            public DestinationAdapter(AvailablePrinterProvider provider) {
-                mProvider = provider;
-                mProvider.registerObserver(mObserver);
+            public DestinationAdapter() {
+                getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+                mFakePdfPrinter = createFakePdfPrinter();
             }
 
             @Override
             public int getCount() {
-                return mProvider.getItemCount();
+                return Math.max(Math.min(mPrinters.size(), DEST_ADAPTER_MAX_ITEM_COUNT),
+                        DEST_ADAPTER_MIN_ITEM_COUNT);
             }
 
             @Override
             public Object getItem(int position) {
-                return mProvider.getItemAt(position);
+                if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+                    return mFakePdfPrinter;
+                }
+                if (!mPrinters.isEmpty()) {
+                    if (position < DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+                        return mPrinters.get(position);
+                    } else if (position > DEST_ADAPTER_POSITION_SAVE_AS_PDF
+                            && position < getCount() - 1) {
+                        return mPrinters.get(position - 1);
+                    }
+                }
+                return null;
             }
 
             @Override
             public long getItemId(int position) {
+                if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+                    return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                }
+                if (mPrinters.isEmpty()) {
+                    if (position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+                        return DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS;
+                    }
+                } else if (position == getCount() - 1) {
+                    return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                }
                 return position;
             }
 
@@ -1455,24 +1765,92 @@
                             R.layout.spinner_dropdown_item, parent, false);
                 }
 
-                PrinterInfo printerInfo = mProvider.getItemAt(position);
-                TextView title = (TextView) convertView.findViewById(R.id.title);
-                title.setText(printerInfo.getName());
+                CharSequence title = null;
+                CharSequence subtitle = null;
 
-                try {
-                    TextView subtitle = (TextView)
-                            convertView.findViewById(R.id.subtitle);
-                    PackageManager pm = getPackageManager();
-                    PackageInfo packageInfo = pm.getPackageInfo(
-                            printerInfo.getId().getServiceName().getPackageName(), 0);
-                    subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
-                    subtitle.setVisibility(View.VISIBLE);
-                } catch (NameNotFoundException nnfe) {
-                    /* ignore */
+                if (mPrinters.isEmpty()
+                        && position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+                        title = getString(R.string.searching_for_printers);
+                } else {
+                    if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+                        PrinterInfo printer = (PrinterInfo) getItem(position);
+                        title = printer.getName();
+                    } else if (position == getCount() - 1) {
+                        title = getString(R.string.all_printers);
+                    } else {
+                        PrinterInfo printer = (PrinterInfo) getItem(position);
+                        title = printer.getName();
+                        try {
+                            PackageInfo packageInfo = getPackageManager().getPackageInfo(
+                                    printer.getId().getServiceName().getPackageName(), 0);
+                            subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+                        } catch (NameNotFoundException nnfe) {
+                            /* ignore */
+                        }
+                    }
+                }
+
+                TextView titleView = (TextView) convertView.findViewById(R.id.title);
+                titleView.setText(title);
+
+                TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+                if (!TextUtils.isEmpty(subtitle)) {
+                    subtitleView.setText(subtitle);
+                    subtitleView.setVisibility(View.VISIBLE);
+                } else {
+                    subtitleView.setText(null);
+                    subtitleView.setVisibility(View.GONE);
                 }
 
                 return convertView;
             }
+
+            @Override
+            public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+                if (id == LOADER_ID_PRINTERS_LOADER) {
+                    return new FusedPrintersProvider(PrintJobConfigActivity.this);
+                }
+                return null;
+            }
+
+            @Override
+            public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+                    List<PrinterInfo> printers) {
+                mPrinters.clear();
+                mPrinters.addAll(printers);
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+                mPrinters.clear();
+                notifyDataSetInvalidated();
+            }
+
+            private PrinterInfo createFakePdfPrinter() {
+                PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
+
+                PrinterCapabilitiesInfo capabilities =
+                        new PrinterCapabilitiesInfo.Builder(printerId)
+                    .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+                            MediaSize.ISO_A4), true)
+                    .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+                            MediaSize.NA_LETTER), false)
+                    .addResolution(new Resolution("PDF resolution", "PDF resolution",
+                            300, 300), true)
+                    .setColorModes(PrintAttributes.COLOR_MODE_COLOR
+                            | PrintAttributes.COLOR_MODE_MONOCHROME,
+                            PrintAttributes.COLOR_MODE_COLOR)
+                    .setOrientations(PrintAttributes.ORIENTATION_PORTRAIT
+                            | PrintAttributes.ORIENTATION_LANDSCAPE,
+                            PrintAttributes.ORIENTATION_PORTRAIT)
+                    .create();
+
+                return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
+                        PrinterInfo.STATUS_READY)
+                    .setCapabilities(capabilities)
+                    .create();
+            }
         }
     }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
deleted file mode 100644
index c2cf65e..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ /dev/null
@@ -1,969 +0,0 @@
-/*
- * 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 com.android.printspooler;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.ParcelFileDescriptor;
-import android.print.IPrintClient;
-import android.print.IPrinterDiscoverySessionObserver;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintAttributes.Tray;
-import android.print.PrintDocumentInfo;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class PrintSpooler {
-
-    private static final String LOG_TAG = "PrintSpooler";
-
-    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
-
-    private static final boolean DEBUG_PERSISTENCE = true;
-
-    private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
-
-    private static final String PRINT_FILE_EXTENSION = "pdf";
-
-    private static int sPrintJobIdCounter;
-
-    private static final Object sLock = new Object();
-
-    private static PrintSpooler sInstance;
-
-    private final Object mLock = new Object();
-
-    private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
-
-    private final PersistenceManager mPersistanceManager;
-
-    private final NotificationController mNotificationController;
-
-    private final PrintSpoolerService mService;
-
-    public static void destroyInstance() {
-        synchronized (sLock) {
-            sInstance = null;
-        }
-    }
-
-    public static void createInstance(PrintSpoolerService service) {
-        synchronized (sLock) {
-            sInstance = new PrintSpooler(service);
-        }
-    }
-
-    public static PrintSpooler peekInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    private PrintSpooler(PrintSpoolerService service) {
-        mService = service;
-        mPersistanceManager = new PersistenceManager(service);
-        mNotificationController = new NotificationController(service);
-        synchronized (mLock) {
-            mPersistanceManager.readStateLocked();
-            handleReadPrintJobsLocked();
-        }
-    }
-
-    public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
-            int state, int appId) {
-        List<PrintJobInfo> foundPrintJobs = null;
-        synchronized (mLock) {
-            final int printJobCount = mPrintJobs.size();
-            for (int i = 0; i < printJobCount; i++) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                PrinterId printerId = printJob.getPrinterId();
-                final boolean sameComponent = (componentName == null
-                        || (printerId != null
-                        && componentName.equals(printerId.getServiceName())));
-                final boolean sameAppId = appId == PrintManager.APP_ID_ANY
-                        || printJob.getAppId() == appId;
-                final boolean sameState = (state == printJob.getState())
-                        || (state == PrintJobInfo.STATE_ANY)
-                        || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
-                                && printJob.getState() > PrintJobInfo.STATE_CREATED);
-                if (sameComponent && sameAppId && sameState) {
-                    if (foundPrintJobs == null) {
-                        foundPrintJobs = new ArrayList<PrintJobInfo>();
-                    }
-                    foundPrintJobs.add(printJob);
-                }
-            }
-        }
-        return foundPrintJobs;
-    }
-
-    public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
-        synchronized (mLock) {
-            final int printJobCount = mPrintJobs.size();
-            for (int i = 0; i < printJobCount; i++) {
-                PrintJobInfo printJob = mPrintJobs.get(i);
-                if (printJob.getId() == printJobId
-                        && (appId == PrintManager.APP_ID_ANY
-                                || appId == printJob.getAppId())) {
-                    return printJob;
-                }
-             }
-             return null;
-        }
-    }
-
-    public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client,
-            PrintAttributes attributes, int appId) {
-        synchronized (mLock) {
-            final int printJobId = generatePrintJobIdLocked();
-            PrintJobInfo printJob = new PrintJobInfo();
-            printJob.setId(printJobId);
-            printJob.setAppId(appId);
-            printJob.setLabel(label);
-            printJob.setAttributes(attributes);
-            printJob.setState(PrintJobInfo.STATE_CREATED);
-
-            addPrintJobLocked(printJob);
-
-            return printJob;
-        }
-    }
-
-    private void handleReadPrintJobsLocked() {
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-
-            // Update the notification.
-            mNotificationController.onPrintJobStateChanged(printJob);
-
-            switch (printJob.getState()) {
-                case PrintJobInfo.STATE_QUEUED:
-                case PrintJobInfo.STATE_STARTED: {
-                    // We have a print job that was queued or started in the past
-                    // but the device battery died or a crash occurred. In this case
-                    // we assume the print job failed and let the user decide whether
-                    // to restart the job or just
-                    setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
-                            mService.getString(R.string.no_connection_to_printer));
-                } break;
-            }
-        }
-    }
-
-    public void checkAllPrintJobsHandled() {
-        synchronized (mLock) {
-            if (!hasActivePrintJobsLocked()) {
-                notifyOnAllPrintJobsHandled();
-            }
-        }
-    }
-
-    public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
-        mService.createPrinterDiscoverySession(observer);
-    }
-
-    private int generatePrintJobIdLocked() {
-        int printJobId = sPrintJobIdCounter++;
-        while (isDuplicatePrintJobId(printJobId)) {
-            printJobId = sPrintJobIdCounter++;
-        }
-        return printJobId;
-    }
-
-    private boolean isDuplicatePrintJobId(int printJobId) {
-        final int printJobCount = mPrintJobs.size();
-        for (int j = 0; j < printJobCount; j++) {
-            PrintJobInfo printJob = mPrintJobs.get(j);
-            if (printJob.getId() == printJobId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
-        final PrintJobInfo printJob;
-        synchronized (mLock) {
-            printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-        }
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                FileInputStream in = null;
-                FileOutputStream out = null;
-                try {
-                    if (printJob != null) {
-                        File file = generateFileForPrintJob(printJobId);
-                        in = new FileInputStream(file);
-                        out = new FileOutputStream(fd.getFileDescriptor());
-                    }
-                    final byte[] buffer = new byte[8192];
-                    while (true) {
-                        final int readByteCount = in.read(buffer);
-                        if (readByteCount < 0) {
-                            return null;
-                        }
-                        out.write(buffer, 0, readByteCount);
-                    }
-                } catch (FileNotFoundException fnfe) {
-                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
-                } catch (IOException ioe) {
-                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
-                } finally {
-                    IoUtils.closeQuietly(in);
-                    IoUtils.closeQuietly(out);
-                    IoUtils.closeQuietly(fd);
-                }
-                Log.i(LOG_TAG, "[END WRITE]");
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    public File generateFileForPrintJob(int printJobId) {
-        return new File(mService.getFilesDir(), "print_job_"
-                + printJobId + "." + PRINT_FILE_EXTENSION);
-    }
-
-    private void addPrintJobLocked(PrintJobInfo printJob) {
-        mPrintJobs.add(printJob);
-        if (DEBUG_PRINT_JOB_LIFECYCLE) {
-            Slog.i(LOG_TAG, "[ADD] " + printJob);
-        }
-    }
-
-    private void removePrintJobLocked(PrintJobInfo printJob) {
-        if (mPrintJobs.remove(printJob)) {
-            generateFileForPrintJob(printJob.getId()).delete();
-            if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                Slog.i(LOG_TAG, "[REMOVE] " + printJob);
-            }
-        }
-    }
-
-    public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
-        boolean success = false;
-
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                success = true;
-
-                printJob.setState(state);
-                printJob.setFailureReason(error);
-                mNotificationController.onPrintJobStateChanged(printJob);
-
-                if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                    Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
-                }
-
-                switch (state) {
-                    case PrintJobInfo.STATE_COMPLETED:
-                    case PrintJobInfo.STATE_CANCELED:
-                        removePrintJobLocked(printJob);
-                        // $fall-through$
-                    case PrintJobInfo.STATE_FAILED: {
-                        PrinterId printerId = printJob.getPrinterId();
-                        if (printerId != null) {
-                            ComponentName service = printerId.getServiceName();
-                            if (!hasActivePrintJobsForServiceLocked(service)) {
-                                mService.onAllPrintJobsForServiceHandled(service);
-                            }
-                        }
-                    } break;
-
-                    case PrintJobInfo.STATE_QUEUED: {
-                        mService.onPrintJobQueued(new PrintJobInfo(printJob));
-                    } break;
-                }
-
-                if (shouldPersistPrintJob(printJob)) {
-                    mPersistanceManager.writeStateLocked();
-                }
-
-                if (!hasActivePrintJobsLocked()) {
-                    notifyOnAllPrintJobsHandled();
-                }
-            }
-        }
-
-        return success;
-    }
-
-    public boolean hasActivePrintJobsLocked() {
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-            if (isActiveState(printJob.getState())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
-        final int printJobCount = mPrintJobs.size();
-        for (int i = 0; i < printJobCount; i++) {
-            PrintJobInfo printJob = mPrintJobs.get(i);
-            if (isActiveState(printJob.getState())
-                    && printJob.getPrinterId().getServiceName().equals(service)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean isActiveState(int printJobState) {
-        return printJobState == PrintJobInfo.STATE_CREATED
-                || printJobState == PrintJobInfo.STATE_QUEUED
-                || printJobState == PrintJobInfo.STATE_STARTED;
-    }
-
-    public boolean setPrintJobTag(int printJobId, String tag) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                String printJobTag = printJob.getTag();
-                if (printJobTag == null) {
-                    if (tag == null) {
-                        return false;
-                    }
-                } else if (printJobTag.equals(tag)) {
-                    return false;
-                }
-                printJob.setTag(tag);
-                if (shouldPersistPrintJob(printJob)) {
-                    mPersistanceManager.writeStateLocked();
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setCopies(copies);
-            }
-        }
-    }
-
-    public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setDocumentInfo(info);
-            }
-        }
-    }
-
-    public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setAttributes(attributes);
-            }
-        }
-    }
-
-    public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPrinterId(printer.getId());
-                printJob.setPrinterName(printer.getName());
-            }
-        }
-    }
-
-    public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPages(pages);
-            }
-        }
-    }
-
-    private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
-        return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
-    }
-
-    private void notifyOnAllPrintJobsHandled() {
-        // This has to run on the tread that is persisting the current state
-        // since this call may result in the system unbinding from the spooler
-        // and as a result the spooler process may get killed before the write
-        // completes.
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                mService.onAllPrintJobsHandled();
-                return null;
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    private final class PersistenceManager {
-        private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
-
-        private static final String TAG_SPOOLER = "spooler";
-        private static final String TAG_JOB = "job";
-
-        private static final String TAG_PRINTER_ID = "printerId";
-        private static final String TAG_PAGE_RANGE = "pageRange";
-        private static final String TAG_ATTRIBUTES = "attributes";
-        private static final String TAG_DOCUMENT_INFO = "documentInfo";
-
-        private static final String ATTR_ID = "id";
-        private static final String ATTR_LABEL = "label";
-        private static final String ATTR_STATE = "state";
-        private static final String ATTR_APP_ID = "appId";
-        private static final String ATTR_USER_ID = "userId";
-        private static final String ATTR_TAG = "tag";
-        private static final String ATTR_COPIES = "copies";
-
-        private static final String TAG_MEDIA_SIZE = "mediaSize";
-        private static final String TAG_RESOLUTION = "resolution";
-        private static final String TAG_MARGINS = "margins";
-        private static final String TAG_INPUT_TRAY = "inputTray";
-        private static final String TAG_OUTPUT_TRAY = "outputTray";
-
-        private static final String ATTR_DUPLEX_MODE = "duplexMode";
-        private static final String ATTR_COLOR_MODE = "colorMode";
-        private static final String ATTR_FITTING_MODE = "fittingMode";
-        private static final String ATTR_ORIENTATION = "orientation";
-
-        private static final String ATTR_LOCAL_ID = "printerName";
-        private static final String ATTR_SERVICE_NAME = "serviceName";
-
-        private static final String ATTR_WIDTH_MILS = "widthMils";
-        private static final String ATTR_HEIGHT_MILS = "heightMils";
-
-        private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
-        private static final String ATTR_VERTICAL_DPI = "verticalDpi";
-
-        private static final String ATTR_LEFT_MILS = "leftMils";
-        private static final String ATTR_TOP_MILS = "topMils";
-        private static final String ATTR_RIGHT_MILS = "rightMils";
-        private static final String ATTR_BOTTOM_MILS = "bottomMils";
-
-        private static final String ATTR_START = "start";
-        private static final String ATTR_END = "end";
-
-        private static final String ATTR_NAME = "name";
-        private static final String ATTR_PAGE_COUNT = "pageCount";
-        private static final String ATTR_CONTENT_TYPE = "contentType";
-
-        private final AtomicFile mStatePersistFile;
-
-        private boolean mWriteStateScheduled;
-
-        private PersistenceManager(Context context) {
-            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
-                    PERSIST_FILE_NAME));
-        }
-
-        public void writeStateLocked() {
-            if (!PERSISTNECE_MANAGER_ENABLED) {
-                return;
-            }
-            if (mWriteStateScheduled) {
-                return;
-            }
-            mWriteStateScheduled = true;
-            new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                    synchronized (mLock) {
-                        mWriteStateScheduled = false;
-                        doWriteStateLocked();
-                    }
-                    return null;
-                }
-            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-        }
-
-        private void doWriteStateLocked() {
-            if (DEBUG_PERSISTENCE) {
-                Log.i(LOG_TAG, "[PERSIST START]");
-            }
-            FileOutputStream out = null;
-            try {
-                out = mStatePersistFile.startWrite();
-
-                XmlSerializer serializer = new FastXmlSerializer();
-                serializer.setOutput(out, "utf-8");
-                serializer.startDocument(null, true);
-                serializer.startTag(null, TAG_SPOOLER);
-
-                List<PrintJobInfo> printJobs = mPrintJobs;
-
-                final int printJobCount = printJobs.size();
-                for (int j = 0; j < printJobCount; j++) {
-                    PrintJobInfo printJob = printJobs.get(j);
-
-                    final int state = printJob.getState();
-                    if (state < PrintJobInfo.STATE_QUEUED
-                            || state > PrintJobInfo.STATE_CANCELED) {
-                        continue;
-                    }
-
-                    serializer.startTag(null, TAG_JOB);
-
-                    serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
-                    serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
-                    serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
-                    serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
-                    serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
-                    String tag = printJob.getTag();
-                    if (tag != null) {
-                        serializer.attribute(null, ATTR_TAG, tag);
-                    }
-                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
-
-                    PrinterId printerId = printJob.getPrinterId();
-                    if (printerId != null) {
-                        serializer.startTag(null, TAG_PRINTER_ID);
-                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
-                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
-                                .flattenToString());
-                        serializer.endTag(null, TAG_PRINTER_ID);
-                    }
-
-                    PageRange[] pages = printJob.getPages();
-                    if (pages != null) {
-                        for (int i = 0; i < pages.length; i++) {
-                            serializer.startTag(null, TAG_PAGE_RANGE);
-                            serializer.attribute(null, ATTR_START, String.valueOf(
-                                    pages[i].getStart()));
-                            serializer.attribute(null, ATTR_END, String.valueOf(
-                                    pages[i].getEnd()));
-                            serializer.endTag(null, TAG_PAGE_RANGE);
-                        }
-                    }
-
-                    PrintAttributes attributes = printJob.getAttributes();
-                    if (attributes != null) {
-                        serializer.startTag(null, TAG_ATTRIBUTES);
-
-                        final int duplexMode = attributes.getDuplexMode();
-                        serializer.attribute(null, ATTR_DUPLEX_MODE,
-                                String.valueOf(duplexMode));
-
-                        final int colorMode = attributes.getColorMode();
-                        serializer.attribute(null, ATTR_COLOR_MODE,
-                                String.valueOf(colorMode));
-
-                        final int fittingMode = attributes.getFittingMode();
-                        serializer.attribute(null, ATTR_FITTING_MODE,
-                                String.valueOf(fittingMode));
-
-                        final int orientation = attributes.getOrientation();
-                        serializer.attribute(null, ATTR_ORIENTATION,
-                                String.valueOf(orientation));
-
-                        MediaSize mediaSize = attributes.getMediaSize();
-                        if (mediaSize != null) {
-                            serializer.startTag(null, TAG_MEDIA_SIZE);
-                            serializer.attribute(null, ATTR_ID, mediaSize.getId());
-                            serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
-                                    .toString());
-                            serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
-                                    mediaSize.getWidthMils()));
-                            serializer.attribute(null, ATTR_HEIGHT_MILS,String.valueOf(
-                                    mediaSize.getHeightMils()));
-                            serializer.endTag(null, TAG_MEDIA_SIZE);
-                        }
-
-                        Resolution resolution = attributes.getResolution();
-                        if (resolution != null) {
-                            serializer.startTag(null, TAG_RESOLUTION);
-                            serializer.attribute(null, ATTR_ID, resolution.getId());
-                            serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
-                                    .toString());
-                            serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
-                                     resolution.getHorizontalDpi()));
-                            serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
-                                    resolution.getVerticalDpi()));
-                            serializer.endTag(null, TAG_RESOLUTION);
-                        }
-
-                        Margins margins = attributes.getMargins();
-                        if (margins != null) {
-                            serializer.startTag(null, TAG_MARGINS);
-                            serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
-                                    margins.getLeftMils()));
-                            serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
-                                    margins.getTopMils()));
-                            serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
-                                    margins.getRightMils()));
-                            serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
-                                    margins.getBottomMils()));
-                            serializer.endTag(null, TAG_MARGINS);
-                        }
-
-                        Tray inputTray = attributes.getInputTray();
-                        if (inputTray != null) {
-                            serializer.startTag(null, TAG_INPUT_TRAY);
-                            serializer.attribute(null, ATTR_ID, inputTray.getId());
-                            serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
-                                    .toString());
-                            serializer.endTag(null, TAG_INPUT_TRAY);
-                        }
-
-                        Tray outputTray = attributes.getOutputTray();
-                        if (outputTray != null) {
-                            serializer.startTag(null, TAG_OUTPUT_TRAY);
-                            serializer.attribute(null, ATTR_ID, outputTray.getId());
-                            serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
-                                    .toString());
-                            serializer.endTag(null, TAG_OUTPUT_TRAY);
-                        }
-
-                        serializer.endTag(null, TAG_ATTRIBUTES);
-                    }
-
-                    PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
-                    if (documentInfo != null) {
-                        serializer.startTag(null, TAG_DOCUMENT_INFO);
-                        serializer.attribute(null, ATTR_NAME, documentInfo.getName());
-                        serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
-                                documentInfo.getContentType()));
-                        serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
-                                documentInfo.getPageCount()));
-                        serializer.endTag(null, TAG_DOCUMENT_INFO);
-                    }
-
-                    serializer.endTag(null, TAG_JOB);
-
-                    if (DEBUG_PERSISTENCE) {
-                        Log.i(LOG_TAG, "[PERSISTED] " + printJob);
-                    }
-                }
-
-                serializer.endTag(null, TAG_SPOOLER);
-                serializer.endDocument();
-                mStatePersistFile.finishWrite(out);
-                if (DEBUG_PERSISTENCE) {
-                    Log.i(LOG_TAG, "[PERSIST END]");
-                }
-            } catch (IOException e) {
-                Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
-                mStatePersistFile.failWrite(out);
-            } finally {
-                IoUtils.closeQuietly(out);
-            }
-        }
-
-        public void readStateLocked() {
-            if (!PERSISTNECE_MANAGER_ENABLED) {
-                return;
-            }
-            FileInputStream in = null;
-            try {
-                in = mStatePersistFile.openRead();
-            } catch (FileNotFoundException e) {
-                Log.i(LOG_TAG, "No existing print spooler state.");
-                return;
-            }
-            try {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(in, null);
-                parseState(parser);
-            } catch (IllegalStateException ise) {
-                Slog.w(LOG_TAG, "Failed parsing ", ise);
-            } catch (NullPointerException npe) {
-                Slog.w(LOG_TAG, "Failed parsing ", npe);
-            } catch (NumberFormatException nfe) {
-                Slog.w(LOG_TAG, "Failed parsing ", nfe);
-            } catch (XmlPullParserException xppe) {
-                Slog.w(LOG_TAG, "Failed parsing ", xppe);
-            } catch (IOException ioe) {
-                Slog.w(LOG_TAG, "Failed parsing ", ioe);
-            } catch (IndexOutOfBoundsException iobe) {
-                Slog.w(LOG_TAG, "Failed parsing ", iobe);
-            } finally {
-                IoUtils.closeQuietly(in);
-            }
-        }
-
-        private void parseState(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            parser.next();
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
-            parser.next();
-
-            while (parsePrintJob(parser)) {
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
-        }
-
-        private boolean parsePrintJob(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            skipEmptyTextTags(parser);
-            if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
-                return false;
-            }
-
-            PrintJobInfo printJob = new PrintJobInfo();
-
-            final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
-            printJob.setId(printJobId);
-            String label = parser.getAttributeValue(null, ATTR_LABEL);
-            printJob.setLabel(label);
-            final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
-            printJob.setState(state);
-            final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
-            printJob.setAppId(appId);
-            final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
-            printJob.setUserId(userId);
-            String tag = parser.getAttributeValue(null, ATTR_TAG);
-            printJob.setTag(tag);
-            String copies = parser.getAttributeValue(null, ATTR_COPIES);
-            printJob.setCopies(Integer.parseInt(copies));
-
-            parser.next();
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
-                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
-                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
-                        null, ATTR_SERVICE_NAME));
-                printJob.setPrinterId(new PrinterId(service, localId));
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            List<PageRange> pageRanges = null;
-            while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
-                final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
-                final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
-                PageRange pageRange = new PageRange(start, end);
-                if (pageRanges == null) {
-                    pageRanges = new ArrayList<PageRange>();
-                }
-                pageRanges.add(pageRange);
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
-                parser.next();
-            }
-            if (pageRanges != null) {
-                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
-                pageRanges.toArray(pageRangesArray);
-                printJob.setPages(pageRangesArray);
-            }
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
-
-                PrintAttributes.Builder builder = new PrintAttributes.Builder();
-
-                String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
-                builder.setDuplexMode(Integer.parseInt(duplexMode));
-
-                String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
-                builder.setColorMode(Integer.parseInt(colorMode));
-
-                String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
-                builder.setFittingMode(Integer.parseInt(fittingMode));
-
-                String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
-                builder.setOrientation(Integer.parseInt(orientation));
-
-                parser.next();
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_WIDTH_MILS));
-                    final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_HEIGHT_MILS));
-                    MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
-                    builder.setMediaSize(mediaSize);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_HORIZONTAL_DPI));
-                    final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_VERTICAL_DPI));
-                    Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
-                    builder.setResolution(resolution);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
-                    final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_LEFT_MILS));
-                    final int topMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_TOP_MILS));
-                    final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_RIGHT_MILS));
-                    final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
-                            ATTR_BOTTOM_MILS));
-                    Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
-                    builder.setMargins(margins);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    Tray tray = new Tray(id, label);
-                    builder.setInputTray(tray);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
-                    parser.next();
-                }
-
-                skipEmptyTextTags(parser);
-                if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
-                    String id = parser.getAttributeValue(null, ATTR_ID);
-                    label = parser.getAttributeValue(null, ATTR_LABEL);
-                    Tray tray = new Tray(id, label);
-                    builder.setOutputTray(tray);
-                    parser.next();
-                    skipEmptyTextTags(parser);
-                    expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
-                    parser.next();
-                }
-
-                printJob.setAttributes(builder.create());
-
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
-                parser.next();
-            }
-
-            skipEmptyTextTags(parser);
-            if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTR_PAGE_COUNT));
-                final int contentType = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTR_CONTENT_TYPE));
-                PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
-                        .setPageCount(pageCount)
-                        .setContentType(contentType).create();
-                printJob.setDocumentInfo(info);
-                parser.next();
-                skipEmptyTextTags(parser);
-                expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
-                parser.next();
-            }
-
-            mPrintJobs.add(printJob);
-
-            if (DEBUG_PERSISTENCE) {
-                Log.i(LOG_TAG, "[RESTORED] " + printJob);
-            }
-
-            skipEmptyTextTags(parser);
-            expect(parser, XmlPullParser.END_TAG, TAG_JOB);
-
-            return true;
-        }
-
-        private void expect(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (!accept(parser, type, tag)) {
-                throw new XmlPullParserException("Exepected event: " + type
-                        + " and tag: " + tag + " but got event: " + parser.getEventType()
-                        + " and tag:" + parser.getName());
-            }
-        }
-
-        private void skipEmptyTextTags(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            while (accept(parser, XmlPullParser.TEXT, null)
-                    && "\n".equals(parser.getText())) {
-                parser.next();
-            }
-        }
-
-        private boolean accept(XmlPullParser parser, int type, String tag)
-                throws IOException, XmlPullParserException {
-            if (parser.getEventType() != type) {
-                return false;
-            }
-            if (tag != null) {
-                if (!tag.equals(parser.getName())) {
-                    return false;
-                }
-            } else if (parser.getName() != null) {
-                return false;
-            }
-            return true;
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 4fab4f8..fda64c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -21,9 +21,8 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.os.Handler;
+import android.os.AsyncTask;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -32,14 +31,38 @@
 import android.print.IPrintSpooler;
 import android.print.IPrintSpoolerCallbacks;
 import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PageRange;
 import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+import android.print.PrintDocumentInfo;
 import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
+import android.util.Xml;
 
+import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.FastXmlSerializer;
 
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -48,22 +71,65 @@
  */
 public final class PrintSpoolerService extends Service {
 
+    private static final String LOG_TAG = "PrintSpoolerService";
+
+    private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
+
+    private static final boolean DEBUG_PERSISTENCE = true;
+
+    private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
+
     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
 
-    private static final String LOG_TAG = "PrintSpoolerService";
+    private static final String PRINT_FILE_EXTENSION = "pdf";
+
+    private static final Object sLock = new Object();
+
+    private final Object mLock = new Object();
+
+    private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+
+    private static PrintSpoolerService sInstance;
+
+    private static int sPrintJobIdCounter;
 
     private Intent mStartPrintJobConfigActivityIntent;
 
     private IPrintSpoolerClient mClient;
 
-    private Handler mHandler;
+    private HandlerCaller mHandlerCaller;
+
+    private PersistenceManager mPersistanceManager;
+
+    private NotificationController mNotificationController;
+
+    private PrinterDiscoverySession mDiscoverySession;
+
+    public static PrintSpoolerService peekInstance() {
+        synchronized (sLock) {
+            return sInstance;
+        }
+    }
 
     @Override
     public void onCreate() {
         super.onCreate();
         mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
                 PrintJobConfigActivity.class);
-        mHandler = new MyHandler(getMainLooper());
+        mHandlerCaller = new HandlerCaller(this, getMainLooper(),
+                new HandlerCallerCallback(), false);
+
+        mPersistanceManager = new PersistenceManager();
+        mNotificationController = new NotificationController(PrintSpoolerService.this);
+
+        synchronized (mLock) {
+            mPersistanceManager.readStateLocked();
+            handleReadPrintJobsLocked();
+        }
+
+        synchronized (sLock) {
+            sInstance = this;
+        }
     }
 
     @Override
@@ -72,10 +138,10 @@
             @Override
             public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
                     ComponentName componentName, int state, int appId, int sequence)
-                            throws RemoteException {
+                    throws RemoteException {
                 List<PrintJobInfo> printJobs = null;
                 try {
-                    printJobs = PrintSpooler.peekInstance().getPrintJobInfos(
+                    printJobs = PrintSpoolerService.this.getPrintJobInfos(
                             componentName, state, appId);
                 } finally {
                     callback.onGetPrintJobInfosResult(printJobs, sequence);
@@ -87,7 +153,7 @@
                     int appId, int sequence) throws RemoteException {
                 PrintJobInfo printJob = null;
                 try {
-                    printJob = PrintSpooler.peekInstance().getPrintJobInfo(printJobId, appId);
+                    printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
                 } finally {
                     callback.onGetPrintJobInfoResult(printJob, sequence);
                 }
@@ -98,11 +164,11 @@
             public void createPrintJob(String printJobName, IPrintClient client,
                     IPrintDocumentAdapter printAdapter, PrintAttributes attributes,
                     IPrintSpoolerCallbacks callback, int appId, int sequence)
-                            throws RemoteException {
+                    throws RemoteException {
                 PrintJobInfo printJob = null;
                 try {
-                    printJob = PrintSpooler.peekInstance().createPrintJob(printJobName, client,
-                            attributes, appId);
+                    printJob = PrintSpoolerService.this.createPrintJob(
+                            printJobName, client, attributes, appId);
                     if (printJob != null) {
                         Intent intent = mStartPrintJobConfigActivityIntent;
                         intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
@@ -113,13 +179,12 @@
 
                         IntentSender sender = PendingIntent.getActivity(
                                 PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
-                                | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+                                        | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
 
-                        SomeArgs args = SomeArgs.obtain();
-                        args.arg1 = client;
-                        args.arg2 = sender;
-                        mHandler.obtainMessage(MyHandler.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
-                                args).sendToTarget();
+                        Message message = mHandlerCaller.obtainMessageOO(
+                                HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
+                                client, sender);
+                        mHandlerCaller.executeOrSendMessage(message);
                     }
                 } finally {
                     callback.onCreatePrintJobResult(printJob, sequence);
@@ -127,11 +192,11 @@
             }
 
             @Override
-            public void setPrintJobState(int printJobId, int state, CharSequence error,
+            public void setPrintJobState(int printJobId, int state, String error,
                     IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
                 boolean success = false;
                 try {
-                    success = PrintSpooler.peekInstance().setPrintJobState(
+                    success = PrintSpoolerService.this.setPrintJobState(
                             printJobId, state, error);
                 } finally {
                     callback.onSetPrintJobStateResult(success, sequece);
@@ -143,7 +208,7 @@
                     IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
                 boolean success = false;
                 try {
-                    success = PrintSpooler.peekInstance().setPrintJobTag(printJobId, tag);
+                    success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
                 } finally {
                     callback.onSetPrintJobTagResult(success, sequece);
                 }
@@ -151,60 +216,191 @@
 
             @Override
             public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
-                PrintSpooler.peekInstance().writePrintJobData(fd, printJobId);
+                PrintSpoolerService.this.writePrintJobData(fd, printJobId);
             }
 
             @Override
             public void setClient(IPrintSpoolerClient client) {
-                mHandler.obtainMessage(MyHandler.MSG_SET_CLIENT, client).sendToTarget();
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_SET_CLIENT, client);
+                mHandlerCaller.executeOrSendMessage(message);
+            }
+
+            @Override
+            public void onPrintersAdded(List<PrinterInfo> printers) {
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINTERS_ADDED, printers);
+                mHandlerCaller.executeOrSendMessage(message);
+            }
+
+            @Override
+            public void onPrintersRemoved(List<PrinterId> printerIds) {
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINTERS_REMOVED, printerIds);
+                mHandlerCaller.executeOrSendMessage(message);
+            }
+
+            @Override
+            public void onPrintersUpdated(List<PrinterInfo> printers) {
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINTERS_UPDATED, printers);
+                mHandlerCaller.executeOrSendMessage(message);
             }
         };
     }
 
-    public void onPrintJobQueued(PrintJobInfo printJob) {
-        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
-                printJob).sendToTarget();
+    public void createPrinterDiscoverySession() {
+        Message message = mHandlerCaller.obtainMessage(
+                HandlerCallerCallback.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
+        mHandlerCaller.executeOrSendMessage(message);
     }
 
-    public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
-        mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
-                observer).sendToTarget();
+    public void destroyPrinterDiscoverySession() {
+        Message message = mHandlerCaller.obtainMessage(
+                HandlerCallerCallback.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+        mHandlerCaller.executeOrSendMessage(message);
     }
 
-    public void onAllPrintJobsForServiceHandled(ComponentName service) {
-        mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
-                service).sendToTarget();
+    public void startPrinterDiscovery(List<PrinterId> priorityList) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_START_PRINTER_DISCOVERY, priorityList);
+        mHandlerCaller.executeOrSendMessage(message);
     }
 
-    public void onAllPrintJobsHandled() {
-        mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+    public void stopPrinterDiscovery() {
+        Message message = mHandlerCaller.obtainMessage(
+                HandlerCallerCallback.MSG_STOP_PRINTER_DISCOVERY);
+        mHandlerCaller.executeOrSendMessage(message);
     }
 
-    private final class MyHandler extends Handler {
-        public static final int MSG_SET_CLIENT = 1;
-        public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2;
-        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 3;
-        public static final int MSG_ON_PRINT_JOB_QUEUED = 5;
-        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 6;
-        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 7;
-        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 9;
+    public void requestPrinterUpdate(PrinterId pritnerId) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_REQUEST_PRINTER_UPDATE, pritnerId);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
 
-        public MyHandler(Looper looper) {
-            super(looper, null, false);
-        }
+
+    private void sendOnPrintJobQueued(PrintJobInfo printJob) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
+        Message message = mHandlerCaller.obtainMessageO(
+                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private void sendOnAllPrintJobsHandled() {
+        Message message = mHandlerCaller.obtainMessage(
+                HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+        mHandlerCaller.executeOrSendMessage(message);
+    }
+
+    private final class HandlerCallerCallback implements HandlerCaller.Callback {
+        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+        public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+        public static final int MSG_START_PRINTER_DISCOVERY = 3;
+        public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+        public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+
+        public static final int MSG_ON_PRINTERS_ADDED = 6;
+        public static final int MSG_ON_PRINTERS_REMOVED = 7;
+        public static final int MSG_ON_PRINTERS_UPDATED = 8;
+
+        public static final int MSG_SET_CLIENT = 9;
+        public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 10;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 11;
+        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 12;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 13;
+        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 14;
 
         @Override
-        public void handleMessage(Message message) {
+        @SuppressWarnings("unchecked")
+        public void executeMessage(Message message) {
             switch (message.what) {
+                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+                    final IPrintSpoolerClient client;
+                    synchronized (mLock) {
+                        client = mClient;
+                    }
+                    if (client != null) {
+                        try {
+                            client.createPrinterDiscoverySession();
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error creating printer discovery session.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+                    final IPrintSpoolerClient client;
+                    synchronized (mLock) {
+                        client = mClient;
+                    }
+                    if (client != null) {
+                        try {
+                            client.destroyPrinterDiscoverySession();
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error destroying printer discovery session.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_START_PRINTER_DISCOVERY: {
+                    final IPrintSpoolerClient client;
+                    synchronized (mLock) {
+                        client = mClient;
+                    }
+                    if (client != null) {
+                        List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+                        try {
+                            client.startPrinterDiscovery(priorityList);
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error starting printer discovery.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_STOP_PRINTER_DISCOVERY: {
+                    final IPrintSpoolerClient client;
+                    synchronized (mLock) {
+                        client = mClient;
+                    }
+                    if (client != null) {
+                        try {
+                            client.stopPrinterDiscovery();
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error stopping printer discovery.", re);
+                        }
+                    }
+                } break;
+
+                case MSG_REQUEST_PRINTER_UPDATE: {
+                    final IPrintSpoolerClient client;
+                    synchronized (mLock) {
+                        client = mClient;
+                    }
+                    if (client != null) {
+                        PrinterId printerId = (PrinterId) message.obj;
+                        try {
+                            client.requestPrinterUpdate(printerId);
+                        } catch (RemoteException re) {
+                            Log.e(LOG_TAG, "Error requesing printer update.", re);
+                        }
+                    }
+                } break;
+
                 case MSG_SET_CLIENT: {
-                    mClient = (IPrintSpoolerClient) message.obj;
-                    if (mClient != null) {
-                        PrintSpooler.createInstance(PrintSpoolerService.this);
-                        mHandler.sendEmptyMessageDelayed(
-                                MyHandler.MSG_CHECK_ALL_PRINTJOBS_HANDLED,
-                                CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
-                    } else {
-                        PrintSpooler.destroyInstance();
+                    synchronized (mLock) {
+                        mClient = (IPrintSpoolerClient) message.obj;
+                        if (mClient != null) {
+                            Message msg = mHandlerCaller.obtainMessage(
+                                    HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
+                            mHandlerCaller.sendMessageDelayed(msg,
+                                    CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
+                        }
                     }
                 } break;
 
@@ -220,18 +416,6 @@
                     }
                 } break;
 
-                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
-                    IPrinterDiscoverySessionObserver observer =
-                            (IPrinterDiscoverySessionObserver) message.obj;
-                    if (mClient != null) {
-                        try {
-                            mClient.createPrinterDiscoverySession(observer);
-                        } catch (RemoteException re) {
-                            Log.e(LOG_TAG, "Error creating printer discovery session.", re);
-                        }
-                    }
-                } break;
-
                 case MSG_ON_PRINT_JOB_QUEUED: {
                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
                     if (mClient != null) {
@@ -266,12 +450,948 @@
                 } break;
 
                 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
-                    PrintSpooler spooler = PrintSpooler.peekInstance();
-                    if (spooler != null) {
-                        spooler.checkAllPrintJobsHandled();
+                    checkAllPrintJobsHandled();
+                } break;
+
+                case MSG_ON_PRINTERS_ADDED: {
+                    final PrinterDiscoverySession session;
+                    synchronized (mLock) {
+                        session = mDiscoverySession;
+                    }
+                    if (session != null) {
+                        List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+                        session.onPrintersAdded(printers);
+                    }
+                } break;
+
+                case MSG_ON_PRINTERS_REMOVED: {
+                    final PrinterDiscoverySession session;
+                    synchronized (mLock) {
+                        session = mDiscoverySession;
+                    }
+                    if (session != null) {
+                        List<PrinterId> printerIds = (ArrayList<PrinterId>) message.obj;
+                        session.onPrintersRemoved(printerIds);
+                    }
+                } break;
+
+                case MSG_ON_PRINTERS_UPDATED: {
+                    final PrinterDiscoverySession session;
+                    synchronized (mLock) {
+                        session = mDiscoverySession;
+                    }
+                    if (session != null) {
+                        List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+                        session.onPrintersUpdated(printers);
                     }
                 } break;
             }
         }
     }
+
+    public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
+            int state, int appId) {
+        List<PrintJobInfo> foundPrintJobs = null;
+        synchronized (mLock) {
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                PrinterId printerId = printJob.getPrinterId();
+                final boolean sameComponent = (componentName == null
+                        || (printerId != null
+                        && componentName.equals(printerId.getServiceName())));
+                final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+                        || printJob.getAppId() == appId;
+                final boolean sameState = (state == printJob.getState())
+                        || (state == PrintJobInfo.STATE_ANY)
+                        || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
+                        && printJob.getState() > PrintJobInfo.STATE_CREATED);
+                if (sameComponent && sameAppId && sameState) {
+                    if (foundPrintJobs == null) {
+                        foundPrintJobs = new ArrayList<PrintJobInfo>();
+                    }
+                    foundPrintJobs.add(printJob);
+                }
+            }
+        }
+        return foundPrintJobs;
+    }
+
+    public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
+        synchronized (mLock) {
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                if (printJob.getId() == printJobId
+                        && (appId == PrintManager.APP_ID_ANY
+                        || appId == printJob.getAppId())) {
+                    return printJob;
+                }
+            }
+            return null;
+        }
+    }
+
+    public PrintJobInfo createPrintJob(String label, IPrintClient client,
+            PrintAttributes attributes, int appId) {
+        synchronized (mLock) {
+            final int printJobId = generatePrintJobIdLocked();
+            PrintJobInfo printJob = new PrintJobInfo();
+            printJob.setId(printJobId);
+            printJob.setAppId(appId);
+            printJob.setLabel(label);
+            printJob.setAttributes(attributes);
+            printJob.setState(PrintJobInfo.STATE_CREATED);
+
+            addPrintJobLocked(printJob);
+
+            return printJob;
+        }
+    }
+
+    private void handleReadPrintJobsLocked() {
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+
+            // Update the notification.
+            mNotificationController.onPrintJobStateChanged(printJob);
+
+            switch (printJob.getState()) {
+                case PrintJobInfo.STATE_QUEUED:
+                case PrintJobInfo.STATE_STARTED: {
+                    // We have a print job that was queued or started in the
+                    // past
+                    // but the device battery died or a crash occurred. In this
+                    // case
+                    // we assume the print job failed and let the user decide
+                    // whether
+                    // to restart the job or just
+                    setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
+                            getString(R.string.no_connection_to_printer));
+                }
+                    break;
+            }
+        }
+    }
+
+    public void checkAllPrintJobsHandled() {
+        synchronized (mLock) {
+            if (!hasActivePrintJobsLocked()) {
+                notifyOnAllPrintJobsHandled();
+            }
+        }
+    }
+
+    private void setPrinterDiscoverySessionClient(PrinterDiscoverySession session) {
+        synchronized (mLock) {
+            mDiscoverySession = session;
+        }
+    }
+
+    private int generatePrintJobIdLocked() {
+        int printJobId = sPrintJobIdCounter++;
+        while (isDuplicatePrintJobId(printJobId)) {
+            printJobId = sPrintJobIdCounter++;
+        }
+        return printJobId;
+    }
+
+    private boolean isDuplicatePrintJobId(int printJobId) {
+        final int printJobCount = mPrintJobs.size();
+        for (int j = 0; j < printJobCount; j++) {
+            PrintJobInfo printJob = mPrintJobs.get(j);
+            if (printJob.getId() == printJobId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+        final PrintJobInfo printJob;
+        synchronized (mLock) {
+            printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+        }
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                FileInputStream in = null;
+                FileOutputStream out = null;
+                try {
+                    if (printJob != null) {
+                        File file = generateFileForPrintJob(printJobId);
+                        in = new FileInputStream(file);
+                        out = new FileOutputStream(fd.getFileDescriptor());
+                    }
+                    final byte[] buffer = new byte[8192];
+                    while (true) {
+                        final int readByteCount = in.read(buffer);
+                        if (readByteCount < 0) {
+                            return null;
+                        }
+                        out.write(buffer, 0, readByteCount);
+                    }
+                } catch (FileNotFoundException fnfe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+                } catch (IOException ioe) {
+                    Log.e(LOG_TAG, "Error writing print job data!", ioe);
+                } finally {
+                    IoUtils.closeQuietly(in);
+                    IoUtils.closeQuietly(out);
+                    IoUtils.closeQuietly(fd);
+                }
+                Log.i(LOG_TAG, "[END WRITE]");
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+
+    public File generateFileForPrintJob(int printJobId) {
+        return new File(getFilesDir(), "print_job_"
+                + printJobId + "." + PRINT_FILE_EXTENSION);
+    }
+
+    private void addPrintJobLocked(PrintJobInfo printJob) {
+        mPrintJobs.add(printJob);
+        if (DEBUG_PRINT_JOB_LIFECYCLE) {
+            Slog.i(LOG_TAG, "[ADD] " + printJob);
+        }
+    }
+
+    private void removePrintJobLocked(PrintJobInfo printJob) {
+        if (mPrintJobs.remove(printJob)) {
+            generateFileForPrintJob(printJob.getId()).delete();
+            if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                Slog.i(LOG_TAG, "[REMOVE] " + printJob);
+            }
+        }
+    }
+
+    public boolean setPrintJobState(int printJobId, int state, String error) {
+        boolean success = false;
+
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                success = true;
+
+                printJob.setState(state);
+                printJob.setFailureReason(error);
+                mNotificationController.onPrintJobStateChanged(printJob);
+
+                if (DEBUG_PRINT_JOB_LIFECYCLE) {
+                    Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+                }
+
+                switch (state) {
+                    case PrintJobInfo.STATE_COMPLETED:
+                    case PrintJobInfo.STATE_CANCELED:
+                        removePrintJobLocked(printJob);
+                        // $fall-through$
+
+                    case PrintJobInfo.STATE_FAILED: {
+                        PrinterId printerId = printJob.getPrinterId();
+                        if (printerId != null) {
+                            ComponentName service = printerId.getServiceName();
+                            if (!hasActivePrintJobsForServiceLocked(service)) {
+                                sendOnAllPrintJobsForServiceHandled(service);
+                            }
+                        }
+                    } break;
+
+                    case PrintJobInfo.STATE_QUEUED: {
+                        sendOnPrintJobQueued(new PrintJobInfo(printJob));
+                    }  break;
+                }
+
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+
+                if (!hasActivePrintJobsLocked()) {
+                    notifyOnAllPrintJobsHandled();
+                }
+            }
+        }
+
+        return success;
+    }
+
+    public boolean hasActivePrintJobsLocked() {
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+            if (isActiveState(printJob.getState())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
+        final int printJobCount = mPrintJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = mPrintJobs.get(i);
+            if (isActiveState(printJob.getState())
+                    && printJob.getPrinterId().getServiceName().equals(service)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isActiveState(int printJobState) {
+        return printJobState == PrintJobInfo.STATE_CREATED
+                || printJobState == PrintJobInfo.STATE_QUEUED
+                || printJobState == PrintJobInfo.STATE_STARTED;
+    }
+
+    public boolean setPrintJobTag(int printJobId, String tag) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                String printJobTag = printJob.getTag();
+                if (printJobTag == null) {
+                    if (tag == null) {
+                        return false;
+                    }
+                } else if (printJobTag.equals(tag)) {
+                    return false;
+                }
+                printJob.setTag(tag);
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setCopies(copies);
+            }
+        }
+    }
+
+    public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setDocumentInfo(info);
+            }
+        }
+    }
+
+    public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setAttributes(attributes);
+            }
+        }
+    }
+
+    public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setPrinterId(printer.getId());
+                printJob.setPrinterName(printer.getName());
+            }
+        }
+    }
+
+    public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setPages(pages);
+            }
+        }
+    }
+
+    private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+        return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
+    }
+
+    private void notifyOnAllPrintJobsHandled() {
+        // This has to run on the tread that is persisting the current state
+        // since this call may result in the system unbinding from the spooler
+        // and as a result the spooler process may get killed before the write
+        // completes.
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                sendOnAllPrintJobsHandled();
+                return null;
+            }
+        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+    }
+
+    private final class PersistenceManager {
+        private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
+
+        private static final String TAG_SPOOLER = "spooler";
+        private static final String TAG_JOB = "job";
+
+        private static final String TAG_PRINTER_ID = "printerId";
+        private static final String TAG_PAGE_RANGE = "pageRange";
+        private static final String TAG_ATTRIBUTES = "attributes";
+        private static final String TAG_DOCUMENT_INFO = "documentInfo";
+
+        private static final String ATTR_ID = "id";
+        private static final String ATTR_LABEL = "label";
+        private static final String ATTR_STATE = "state";
+        private static final String ATTR_APP_ID = "appId";
+        private static final String ATTR_USER_ID = "userId";
+        private static final String ATTR_TAG = "tag";
+        private static final String ATTR_COPIES = "copies";
+
+        private static final String TAG_MEDIA_SIZE = "mediaSize";
+        private static final String TAG_RESOLUTION = "resolution";
+        private static final String TAG_MARGINS = "margins";
+        private static final String TAG_INPUT_TRAY = "inputTray";
+        private static final String TAG_OUTPUT_TRAY = "outputTray";
+
+        private static final String ATTR_DUPLEX_MODE = "duplexMode";
+        private static final String ATTR_COLOR_MODE = "colorMode";
+        private static final String ATTR_FITTING_MODE = "fittingMode";
+        private static final String ATTR_ORIENTATION = "orientation";
+
+        private static final String ATTR_LOCAL_ID = "printerName";
+        private static final String ATTR_SERVICE_NAME = "serviceName";
+
+        private static final String ATTR_WIDTH_MILS = "widthMils";
+        private static final String ATTR_HEIGHT_MILS = "heightMils";
+
+        private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
+        private static final String ATTR_VERTICAL_DPI = "verticalDpi";
+
+        private static final String ATTR_LEFT_MILS = "leftMils";
+        private static final String ATTR_TOP_MILS = "topMils";
+        private static final String ATTR_RIGHT_MILS = "rightMils";
+        private static final String ATTR_BOTTOM_MILS = "bottomMils";
+
+        private static final String ATTR_START = "start";
+        private static final String ATTR_END = "end";
+
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_PAGE_COUNT = "pageCount";
+        private static final String ATTR_CONTENT_TYPE = "contentType";
+
+        private final AtomicFile mStatePersistFile;
+
+        private boolean mWriteStateScheduled;
+
+        private PersistenceManager() {
+            mStatePersistFile = new AtomicFile(new File(getFilesDir(),
+                    PERSIST_FILE_NAME));
+        }
+
+        public void writeStateLocked() {
+            if (!PERSISTNECE_MANAGER_ENABLED) {
+                return;
+            }
+            if (mWriteStateScheduled) {
+                return;
+            }
+            mWriteStateScheduled = true;
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (mLock) {
+                        mWriteStateScheduled = false;
+                        doWriteStateLocked();
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+        }
+
+        private void doWriteStateLocked() {
+            if (DEBUG_PERSISTENCE) {
+                Log.i(LOG_TAG, "[PERSIST START]");
+            }
+            FileOutputStream out = null;
+            try {
+                out = mStatePersistFile.startWrite();
+
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(out, "utf-8");
+                serializer.startDocument(null, true);
+                serializer.startTag(null, TAG_SPOOLER);
+
+                List<PrintJobInfo> printJobs = mPrintJobs;
+
+                final int printJobCount = printJobs.size();
+                for (int j = 0; j < printJobCount; j++) {
+                    PrintJobInfo printJob = printJobs.get(j);
+
+                    final int state = printJob.getState();
+                    if (state < PrintJobInfo.STATE_QUEUED
+                            || state > PrintJobInfo.STATE_CANCELED) {
+                        continue;
+                    }
+
+                    serializer.startTag(null, TAG_JOB);
+
+                    serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
+                    serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
+                    serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
+                    serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
+                    serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
+                    String tag = printJob.getTag();
+                    if (tag != null) {
+                        serializer.attribute(null, ATTR_TAG, tag);
+                    }
+                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
+
+                    PrinterId printerId = printJob.getPrinterId();
+                    if (printerId != null) {
+                        serializer.startTag(null, TAG_PRINTER_ID);
+                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+                                .flattenToString());
+                        serializer.endTag(null, TAG_PRINTER_ID);
+                    }
+
+                    PageRange[] pages = printJob.getPages();
+                    if (pages != null) {
+                        for (int i = 0; i < pages.length; i++) {
+                            serializer.startTag(null, TAG_PAGE_RANGE);
+                            serializer.attribute(null, ATTR_START, String.valueOf(
+                                    pages[i].getStart()));
+                            serializer.attribute(null, ATTR_END, String.valueOf(
+                                    pages[i].getEnd()));
+                            serializer.endTag(null, TAG_PAGE_RANGE);
+                        }
+                    }
+
+                    PrintAttributes attributes = printJob.getAttributes();
+                    if (attributes != null) {
+                        serializer.startTag(null, TAG_ATTRIBUTES);
+
+                        final int duplexMode = attributes.getDuplexMode();
+                        serializer.attribute(null, ATTR_DUPLEX_MODE,
+                                String.valueOf(duplexMode));
+
+                        final int colorMode = attributes.getColorMode();
+                        serializer.attribute(null, ATTR_COLOR_MODE,
+                                String.valueOf(colorMode));
+
+                        final int fittingMode = attributes.getFittingMode();
+                        serializer.attribute(null, ATTR_FITTING_MODE,
+                                String.valueOf(fittingMode));
+
+                        final int orientation = attributes.getOrientation();
+                        serializer.attribute(null, ATTR_ORIENTATION,
+                                String.valueOf(orientation));
+
+                        MediaSize mediaSize = attributes.getMediaSize();
+                        if (mediaSize != null) {
+                            serializer.startTag(null, TAG_MEDIA_SIZE);
+                            serializer.attribute(null, ATTR_ID, mediaSize.getId());
+                            serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
+                                    .toString());
+                            serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
+                                    mediaSize.getWidthMils()));
+                            serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
+                                    mediaSize.getHeightMils()));
+                            serializer.endTag(null, TAG_MEDIA_SIZE);
+                        }
+
+                        Resolution resolution = attributes.getResolution();
+                        if (resolution != null) {
+                            serializer.startTag(null, TAG_RESOLUTION);
+                            serializer.attribute(null, ATTR_ID, resolution.getId());
+                            serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
+                                    .toString());
+                            serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
+                                    resolution.getHorizontalDpi()));
+                            serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
+                                    resolution.getVerticalDpi()));
+                            serializer.endTag(null, TAG_RESOLUTION);
+                        }
+
+                        Margins margins = attributes.getMargins();
+                        if (margins != null) {
+                            serializer.startTag(null, TAG_MARGINS);
+                            serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
+                                    margins.getLeftMils()));
+                            serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
+                                    margins.getTopMils()));
+                            serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
+                                    margins.getRightMils()));
+                            serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
+                                    margins.getBottomMils()));
+                            serializer.endTag(null, TAG_MARGINS);
+                        }
+
+                        Tray inputTray = attributes.getInputTray();
+                        if (inputTray != null) {
+                            serializer.startTag(null, TAG_INPUT_TRAY);
+                            serializer.attribute(null, ATTR_ID, inputTray.getId());
+                            serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
+                                    .toString());
+                            serializer.endTag(null, TAG_INPUT_TRAY);
+                        }
+
+                        Tray outputTray = attributes.getOutputTray();
+                        if (outputTray != null) {
+                            serializer.startTag(null, TAG_OUTPUT_TRAY);
+                            serializer.attribute(null, ATTR_ID, outputTray.getId());
+                            serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
+                                    .toString());
+                            serializer.endTag(null, TAG_OUTPUT_TRAY);
+                        }
+
+                        serializer.endTag(null, TAG_ATTRIBUTES);
+                    }
+
+                    PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
+                    if (documentInfo != null) {
+                        serializer.startTag(null, TAG_DOCUMENT_INFO);
+                        serializer.attribute(null, ATTR_NAME, documentInfo.getName());
+                        serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
+                                documentInfo.getContentType()));
+                        serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
+                                documentInfo.getPageCount()));
+                        serializer.endTag(null, TAG_DOCUMENT_INFO);
+                    }
+
+                    serializer.endTag(null, TAG_JOB);
+
+                    if (DEBUG_PERSISTENCE) {
+                        Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+                    }
+                }
+
+                serializer.endTag(null, TAG_SPOOLER);
+                serializer.endDocument();
+                mStatePersistFile.finishWrite(out);
+                if (DEBUG_PERSISTENCE) {
+                    Log.i(LOG_TAG, "[PERSIST END]");
+                }
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+                mStatePersistFile.failWrite(out);
+            } finally {
+                IoUtils.closeQuietly(out);
+            }
+        }
+
+        public void readStateLocked() {
+            if (!PERSISTNECE_MANAGER_ENABLED) {
+                return;
+            }
+            FileInputStream in = null;
+            try {
+                in = mStatePersistFile.openRead();
+            } catch (FileNotFoundException e) {
+                Log.i(LOG_TAG, "No existing print spooler state.");
+                return;
+            }
+            try {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(in, null);
+                parseState(parser);
+            } catch (IllegalStateException ise) {
+                Slog.w(LOG_TAG, "Failed parsing ", ise);
+            } catch (NullPointerException npe) {
+                Slog.w(LOG_TAG, "Failed parsing ", npe);
+            } catch (NumberFormatException nfe) {
+                Slog.w(LOG_TAG, "Failed parsing ", nfe);
+            } catch (XmlPullParserException xppe) {
+                Slog.w(LOG_TAG, "Failed parsing ", xppe);
+            } catch (IOException ioe) {
+                Slog.w(LOG_TAG, "Failed parsing ", ioe);
+            } catch (IndexOutOfBoundsException iobe) {
+                Slog.w(LOG_TAG, "Failed parsing ", iobe);
+            } finally {
+                IoUtils.closeQuietly(in);
+            }
+        }
+
+        private void parseState(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            parser.next();
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
+            parser.next();
+
+            while (parsePrintJob(parser)) {
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
+        }
+
+        private boolean parsePrintJob(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            skipEmptyTextTags(parser);
+            if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
+                return false;
+            }
+
+            PrintJobInfo printJob = new PrintJobInfo();
+
+            final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
+            printJob.setId(printJobId);
+            String label = parser.getAttributeValue(null, ATTR_LABEL);
+            printJob.setLabel(label);
+            final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
+            printJob.setState(state);
+            final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
+            printJob.setAppId(appId);
+            final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
+            printJob.setUserId(userId);
+            String tag = parser.getAttributeValue(null, ATTR_TAG);
+            printJob.setTag(tag);
+            String copies = parser.getAttributeValue(null, ATTR_COPIES);
+            printJob.setCopies(Integer.parseInt(copies));
+
+            parser.next();
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
+                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+                        null, ATTR_SERVICE_NAME));
+                printJob.setPrinterId(new PrinterId(service, localId));
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            List<PageRange> pageRanges = null;
+            while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
+                final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
+                final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
+                PageRange pageRange = new PageRange(start, end);
+                if (pageRanges == null) {
+                    pageRanges = new ArrayList<PageRange>();
+                }
+                pageRanges.add(pageRange);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
+                parser.next();
+            }
+            if (pageRanges != null) {
+                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+                pageRanges.toArray(pageRangesArray);
+                printJob.setPages(pageRangesArray);
+            }
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
+
+                PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+                String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
+                builder.setDuplexMode(Integer.parseInt(duplexMode));
+
+                String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
+                builder.setColorMode(Integer.parseInt(colorMode));
+
+                String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
+                builder.setFittingMode(Integer.parseInt(fittingMode));
+
+                String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
+                builder.setOrientation(Integer.parseInt(orientation));
+
+                parser.next();
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_WIDTH_MILS));
+                    final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_HEIGHT_MILS));
+                    MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
+                    builder.setMediaSize(mediaSize);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_HORIZONTAL_DPI));
+                    final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_VERTICAL_DPI));
+                    Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
+                    builder.setResolution(resolution);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
+                    final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_LEFT_MILS));
+                    final int topMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_TOP_MILS));
+                    final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_RIGHT_MILS));
+                    final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
+                            ATTR_BOTTOM_MILS));
+                    Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
+                    builder.setMargins(margins);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    Tray tray = new Tray(id, label);
+                    builder.setInputTray(tray);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
+                    parser.next();
+                }
+
+                skipEmptyTextTags(parser);
+                if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    label = parser.getAttributeValue(null, ATTR_LABEL);
+                    Tray tray = new Tray(id, label);
+                    builder.setOutputTray(tray);
+                    parser.next();
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
+                    parser.next();
+                }
+
+                printJob.setAttributes(builder.create());
+
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_PAGE_COUNT));
+                final int contentType = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_CONTENT_TYPE));
+                PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
+                        .setPageCount(pageCount)
+                        .setContentType(contentType).create();
+                printJob.setDocumentInfo(info);
+                parser.next();
+                skipEmptyTextTags(parser);
+                expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
+                parser.next();
+            }
+
+            mPrintJobs.add(printJob);
+
+            if (DEBUG_PERSISTENCE) {
+                Log.i(LOG_TAG, "[RESTORED] " + printJob);
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_JOB);
+
+            return true;
+        }
+
+        private void expect(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (!accept(parser, type, tag)) {
+                throw new XmlPullParserException("Exepected event: " + type
+                        + " and tag: " + tag + " but got event: " + parser.getEventType()
+                        + " and tag:" + parser.getName());
+            }
+        }
+
+        private void skipEmptyTextTags(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            while (accept(parser, XmlPullParser.TEXT, null)
+                    && "\n".equals(parser.getText())) {
+                parser.next();
+            }
+        }
+
+        private boolean accept(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (parser.getEventType() != type) {
+                return false;
+            }
+            if (tag != null) {
+                if (!tag.equals(parser.getName())) {
+                    return false;
+                }
+            } else if (parser.getName() != null) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    public static abstract class PrinterDiscoverySession {
+
+        private PrintSpoolerService mService;
+
+        private boolean mIsStarted;
+
+        public PrinterDiscoverySession() {
+            mService = PrintSpoolerService.peekInstance();
+            mService.createPrinterDiscoverySession();
+            mService.setPrinterDiscoverySessionClient(this);
+        }
+
+        public final void startPrinterDisovery(List<PrinterId> priorityList) {
+            mIsStarted = true;
+            mService.startPrinterDiscovery(priorityList);
+        }
+
+        public final void stopPrinterDiscovery() {
+            mIsStarted = false;
+            mService.stopPrinterDiscovery();
+        }
+
+        public void requestPrinterUpdated(PrinterId printerId) {
+            mService.requestPrinterUpdate(printerId);
+        }
+
+        public final void destroy() {
+            mService.setPrinterDiscoverySessionClient(null);
+            mService.destroyPrinterDiscoverySession();
+        }
+
+        public final boolean isStarted() {
+            return mIsStarted;
+        }
+
+        public abstract void onPrintersAdded(List<PrinterInfo> printers);
+
+        public abstract void onPrintersRemoved(List<PrinterId> printerIds);
+
+        public abstract void onPrintersUpdated(List<PrinterInfo> printers);
+    }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
new file mode 100644
index 0000000..141dbd1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.printspooler;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.print.PrinterId;
+
+import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
+
+public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.select_printer_activity);
+    }
+
+    @Override
+    public void onPrinterSelected(PrinterId printer) {
+        Intent intent = new Intent();
+        intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
new file mode 100644
index 0000000..9ca3a86
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
@@ -0,0 +1,401 @@
+/*
+ * 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 com.android.printspooler;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a fragment for selecting a printer.
+ */
+public final class SelectPrinterFragment extends ListFragment {
+
+    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+    private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
+            "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
+
+    private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
+            "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
+
+    private final ArrayList<PrintServiceInfo> mAddPrinterServices =
+            new ArrayList<PrintServiceInfo>();
+
+    public static interface OnPrinterSelectedListener {
+        public void onPrinterSelected(PrinterId printerId);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setListAdapter(new DestinationAdapter());
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.select_printer_activity, menu);
+
+        MenuItem searchItem = menu.findItem(R.id.action_search);
+        SearchView searchView = (SearchView) searchItem.getActionView();
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(String query) {
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(String searchString) {
+                ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString);
+                return true;
+            }
+        });
+
+        if (mAddPrinterServices.isEmpty()) {
+            menu.removeItem(R.id.action_add_printer);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        updateAddPrintersAdapter();
+        getActivity().invalidateOptionsMenu();
+        super.onResume();
+    }
+
+    @Override
+    public void onListItemClick(ListView list, View view, int position, long id) {
+        PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
+        Activity activity = getActivity();
+        if (activity instanceof OnPrinterSelectedListener) {
+            ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
+        } else {
+            throw new IllegalStateException("the host activity must implement"
+                    + " OnPrinterSelectedListener");
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.action_add_printer) {
+            showAddPrinterSelectionDialog();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateAddPrintersAdapter() {
+        mAddPrinterServices.clear();
+
+        // Get all print services.
+        List<ResolveInfo> resolveInfos = getActivity().getPackageManager().queryIntentServices(
+                new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+
+        // No print services - done.
+        if (resolveInfos.isEmpty()) {
+            return;
+        }
+
+        // Find the services with valid add printers activities.
+        final int resolveInfoCount = resolveInfos.size();
+        for (int i = 0; i < resolveInfoCount; i++) {
+            ResolveInfo resolveInfo = resolveInfos.get(i);
+
+            PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
+                    resolveInfo, getActivity());
+            String addPrintersActivity = printServiceInfo.getAddPrintersActivityName();
+
+            // No add printers activity declared - done.
+            if (TextUtils.isEmpty(addPrintersActivity)) {
+                continue;
+            }
+
+            ComponentName addPrintersComponentName = new ComponentName(
+                    resolveInfo.serviceInfo.packageName,
+                    addPrintersActivity);
+            Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN)
+                .setComponent(addPrintersComponentName);
+
+            // The add printers activity is valid - add it.
+            if (!getActivity().getPackageManager().queryIntentActivities(
+                    addPritnersIntent, 0).isEmpty()) {
+                mAddPrinterServices.add(printServiceInfo);
+            }
+        }
+    }
+
+    private void showAddPrinterSelectionDialog() {
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        Fragment oldFragment = getFragmentManager().findFragmentByTag(
+                FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+        if (oldFragment != null) {
+            transaction.remove(oldFragment);
+        }
+        AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
+        Bundle arguments = new Bundle();
+        arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
+                mAddPrinterServices);
+        newFragment.setArguments(arguments);
+        transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+        transaction.commit();
+    }
+
+    public static class AddPrinterAlertDialogFragment extends DialogFragment {
+
+        private static final String DEFAULT_MARKET_QUERY_STRING =
+                "market://search?q=print";
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                    .setTitle(R.string.choose_print_service);
+
+            final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
+                    getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
+
+            ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+                    android.R.layout.simple_list_item_1);
+            final int printServiceCount = printServices.size();
+            for (int i = 0; i < printServiceCount; i++) {
+                PrintServiceInfo printService = printServices.get(i);
+                adapter.add(printService.getResolveInfo().loadLabel(
+                        getActivity().getPackageManager()).toString());
+            }
+
+            builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    PrintServiceInfo printService = printServices.get(which);
+                    ComponentName componentName = new ComponentName(
+                            printService.getResolveInfo().serviceInfo.packageName,
+                            printService.getAddPrintersActivityName());
+                    Intent intent = new Intent(Intent.ACTION_MAIN);
+                    intent.setComponent(componentName);
+                    startActivity(intent);
+                }
+            });
+
+            Uri marketUri = Uri.parse(DEFAULT_MARKET_QUERY_STRING);
+            final Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
+            if (getActivity().getPackageManager().resolveActivity(marketIntent, 0) != null) {
+                builder.setPositiveButton(R.string.search_play_store,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int whichButton) {
+                            startActivity(marketIntent);
+                        }
+                    });
+            }
+
+            return builder.create();
+        }
+    }
+
+    private final class DestinationAdapter extends BaseAdapter
+            implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
+
+        private final Object mLock = new Object();
+
+        private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+
+        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
+
+        private CharSequence mLastSearchString;
+
+        public DestinationAdapter() {
+            getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+        }
+
+        @Override
+        public Filter getFilter() {
+            return new Filter() {
+                @Override
+                protected FilterResults performFiltering(CharSequence constraint) {
+                    synchronized (mLock) {
+                        if (TextUtils.isEmpty(constraint)) {
+                            return null;
+                        }
+                        FilterResults results = new FilterResults();
+                        List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
+                        String constraintLowerCase = constraint.toString().toLowerCase();
+                        final int printerCount = mPrinters.size();
+                        for (int i = 0; i < printerCount; i++) {
+                            PrinterInfo printer = mPrinters.get(i);
+                            if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+                                filteredPrinters.add(printer);
+                            }
+                        }
+                        results.values = filteredPrinters;
+                        results.count = filteredPrinters.size();
+                        return results;
+                    }
+                }
+
+                @Override
+                @SuppressWarnings("unchecked")
+                protected void publishResults(CharSequence constraint, FilterResults results) {
+                    synchronized (mLock) {
+                        mLastSearchString = constraint;
+                        mFilteredPrinters.clear();
+                        if (results == null) {
+                            mFilteredPrinters.addAll(mPrinters);
+                        } else {
+                            List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
+                            mFilteredPrinters.addAll(printers);
+                        }
+                    }
+                    notifyDataSetChanged();
+                }
+            };
+        }
+
+        @Override
+        public int getCount() {
+            synchronized (mLock) {
+                return mFilteredPrinters.size();
+            }
+        }
+
+        @Override
+        public Object getItem(int position) {
+            synchronized (mLock) {
+                return mFilteredPrinters.get(position);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView,
+                ViewGroup parent) {
+            return getView(position, convertView, parent);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getActivity().getLayoutInflater().inflate(
+                        R.layout.spinner_dropdown_item, parent, false);
+            }
+
+            CharSequence title = null;
+            CharSequence subtitle = null;
+
+            PrinterInfo printer = (PrinterInfo) getItem(position);
+            title = printer.getName();
+            try {
+                PackageManager pm = getActivity().getPackageManager();
+                PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
+                        .getServiceName().getPackageName(), 0);
+                subtitle = packageInfo.applicationInfo.loadLabel(pm);
+            } catch (NameNotFoundException nnfe) {
+                /* ignore */
+            }
+
+            TextView titleView = (TextView) convertView.findViewById(R.id.title);
+            titleView.setText(title);
+
+            TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+            if (!TextUtils.isEmpty(subtitle)) {
+                subtitleView.setText(subtitle);
+                subtitleView.setVisibility(View.VISIBLE);
+            } else {
+                subtitleView.setText(null);
+                subtitleView.setVisibility(View.GONE);
+            }
+
+            return convertView;
+        }
+
+        @Override
+        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+            if (id == LOADER_ID_PRINTERS_LOADER) {
+                return new FusedPrintersProvider(getActivity());
+            }
+            return null;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+                List<PrinterInfo> printers) {
+            synchronized (mLock) {
+                mPrinters.clear();
+                mPrinters.addAll(printers);
+                mFilteredPrinters.clear();
+                mFilteredPrinters.addAll(printers);
+                if (!TextUtils.isEmpty(mLastSearchString)) {
+                    getFilter().filter(mLastSearchString);
+                }
+            }
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+            synchronized (mLock) {
+                mPrinters.clear();
+                mFilteredPrinters.clear();
+            }
+            notifyDataSetInvalidated();
+        }
+    }
+}
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 491dddc..5c68460 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -30,8 +30,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintJobInfo;
 import android.print.PrintManager;
 import android.print.PrinterId;
@@ -79,6 +77,10 @@
 
     private boolean mDestroyed;
 
+    private boolean mAllPrintJobsHandled;
+
+    private boolean mHasPrinterDiscoverySession;
+
     public RemotePrintService(Context context, ComponentName componentName, int userId,
             RemotePrintSpooler spooler) {
         mContext = context;
@@ -97,6 +99,8 @@
     private void handleDestroy() {
         throwIfDestroyed();
         ensureUnbound();
+        mAllPrintJobsHandled = false;
+        mHasPrinterDiscoverySession = false;
         mDestroyed = true;
     }
 
@@ -110,18 +114,27 @@
     }
 
     private void handleBinderDied() {
+        mAllPrintJobsHandled = false;
+        mHasPrinterDiscoverySession = false;
         mPendingCommands.clear();
         ensureUnbound();
     }
 
     private void handleOnAllPrintJobsHandled() {
         throwIfDestroyed();
+
+        mAllPrintJobsHandled = true;
+
         if (isBound()) {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()");
             }
-            // If bound and all the work is completed, then unbind.
-            ensureUnbound();
+
+            // If the service has a printer discovery session
+            // created we should not disconnect from it just yet.
+            if (!mHasPrinterDiscoverySession) {
+                ensureUnbound();
+            }
         }
     }
 
@@ -153,6 +166,9 @@
 
     private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
         throwIfDestroyed();
+
+        mAllPrintJobsHandled = false;
+
         if (!isBound()) {
             ensureBound();
             mPendingCommands.add(new Runnable() {
@@ -173,20 +189,18 @@
         }
     }
 
-    public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
-        mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
-                observer).sendToTarget();
+    public void createPrinterDiscoverySession() {
+        mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
     }
 
-    private void handleCreatePrinterDiscoverySession(
-            final IPrinterDiscoverySessionObserver observer) {
+    private void handleCreatePrinterDiscoverySession() {
         throwIfDestroyed();
         if (!isBound()) {
             ensureBound();
             mPendingCommands.add(new Runnable() {
                 @Override
                 public void run() {
-                    handleCreatePrinterDiscoverySession(observer);
+                    handleCreatePrinterDiscoverySession();
                 }
             });
         } else {
@@ -194,9 +208,126 @@
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
             }
             try {
-                mPrintService.createPrinterDiscoverySession(observer);
+                mPrintService.createPrinterDiscoverySession();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
+                Slog.e(LOG_TAG, "Error creating printer dicovery session.", re);
+            }
+
+            mHasPrinterDiscoverySession = true;
+        }
+    }
+
+    public void destroyPrinterDiscoverySession() {
+        mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+    }
+
+    private void handleDestroyPrinterDiscoverySession() {
+        throwIfDestroyed();
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(new Runnable() {
+                @Override
+                public void run() {
+                    handleDestroyPrinterDiscoverySession();
+                }
+            });
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()");
+            }
+
+            mHasPrinterDiscoverySession = false;
+
+            try {
+                mPrintService.destroyPrinterDiscoverySession();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re);
+            }
+
+            // If the service has no print jobs and no active discovery
+            // session anymore we should disconnect from it.
+            if (mAllPrintJobsHandled) {
+                ensureUnbound();
+            }
+        }
+    }
+
+    public void startPrinterDiscovery(List<PrinterId> priorityList) {
+        mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+                priorityList).sendToTarget();
+    }
+
+    private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
+        throwIfDestroyed();
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(new Runnable() {
+                @Override
+                public void run() {
+                    handleStartPrinterDiscovery(priorityList);
+                }
+            });
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()");
+            }
+            try {
+                mPrintService.startPrinterDiscovery(priorityList);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error starting printer dicovery.", re);
+            }
+        }
+    }
+
+    public void stopPrinterDiscovery() {
+        mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
+    }
+
+    private void handleStopPrinterDiscovery() {
+        throwIfDestroyed();
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(new Runnable() {
+                @Override
+                public void run() {
+                    handleStopPrinterDiscovery();
+                }
+            });
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
+            }
+            try {
+                mPrintService.stopPrinterDiscovery();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error stopping printer dicovery.", re);
+            }
+        }
+    }
+
+    public void requestPrinterUpdate(PrinterId printerId) {
+        mHandler.obtainMessage(MyHandler.MSG_REQUEST_PRINTER_UPDATE,
+                printerId).sendToTarget();
+    }
+
+    private void handleRequestPrinterUpdate(final PrinterId printerId) {
+        throwIfDestroyed();
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(new Runnable() {
+                @Override
+                public void run() {
+                    handleRequestPrinterUpdate(printerId);
+                }
+            });
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] requestPrinterUpdate()");
+            }
+            try {
+                mPrintService.requestPrinterUpdate(printerId);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error requesting a printer update.", re);
             }
         }
     }
@@ -279,20 +410,47 @@
     }
 
     private final class MyHandler extends Handler {
-        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
-        public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
-        public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
-        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4;
-        public static final int MSG_DESTROY = 6;
-        public static final int MSG_BINDER_DIED = 7;
+        public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+        public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+        public static final int MSG_START_PRINTER_DISCOVERY = 3;
+        public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+        public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 6;
+        public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 7;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 8;
+        public static final int MSG_DESTROY = 9;
+        public static final int MSG_BINDER_DIED = 10;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
         }
 
         @Override
+        @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
             switch (message.what) {
+                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+                    handleCreatePrinterDiscoverySession();
+                } break;
+
+                case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+                    handleDestroyPrinterDiscoverySession();
+                } break;
+
+                case MSG_START_PRINTER_DISCOVERY: {
+                    List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+                    handleStartPrinterDiscovery(priorityList);
+                } break;
+
+                case MSG_STOP_PRINTER_DISCOVERY: {
+                    handleStopPrinterDiscovery();
+                } break;
+
+                case MSG_REQUEST_PRINTER_UPDATE: {
+                    PrinterId printerId = (PrinterId) message.obj;
+                    handleRequestPrinterUpdate(printerId);
+                } break;
+
                 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
                     handleOnAllPrintJobsHandled();
                 } break;
@@ -307,13 +465,6 @@
                     handleOnPrintJobQueued(printJob);
                 } break;
 
-                case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
-                    IPrinterDiscoverySessionObserver observer =
-                            (IPrinterDiscoverySessionObserver) message.obj;
-                    handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
-                            mComponentName, observer));
-                } break;
-
                 case MSG_DESTROY: {
                     handleDestroy();
                 } break;
@@ -363,7 +514,7 @@
         }
 
         @Override
-        public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+        public boolean setPrintJobState(int printJobId, int state, String error) {
             RemotePrintService service = mWeakService.get();
             if (service != null) {
                 final long identity = Binder.clearCallingIdentity();
@@ -402,79 +553,70 @@
                 }
             }
         }
-    }
-
-    private static final class SecurePrinterDiscoverySessionObserver
-            extends IPrinterDiscoverySessionObserver.Stub {
-        private final ComponentName mComponentName;
-
-        private final IPrinterDiscoverySessionObserver mDecoratedObsever;
-
-        public SecurePrinterDiscoverySessionObserver(ComponentName componentName,
-                IPrinterDiscoverySessionObserver observer) {
-            mComponentName = componentName;
-            mDecoratedObsever = observer;
-        }
 
         @Override
         public void onPrintersAdded(List<PrinterInfo> printers) {
-            throwIfPrinterIdsForPrinterInfoTampered(printers);
-            try {
-                mDecoratedObsever.onPrintersAdded(printers);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
-            }
-        }
-
-        @Override
-        public void onPrintersUpdated(List<PrinterInfo> printers) {
-            throwIfPrinterIdsForPrinterInfoTampered(printers);
-            try {
-                mDecoratedObsever.onPrintersUpdated(printers);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
+            RemotePrintService service = mWeakService.get();
+            if (service != null) {
+                throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    service.mSpooler.onPrintersAdded(printers);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
 
         @Override
         public void onPrintersRemoved(List<PrinterId> printerIds) {
-            throwIfPrinterIdsTampered(printerIds);
-            try {
-                mDecoratedObsever.onPrintersRemoved(printerIds);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
+            RemotePrintService service = mWeakService.get();
+            if (service != null) {
+                throwIfPrinterIdsTampered(service.mComponentName, printerIds);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    service.mSpooler.onPrintersRemoved(printerIds);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
 
         @Override
-        public void setController(IPrinterDiscoverySessionController controller) {
-            try {
-                mDecoratedObsever.setController(controller);
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error setting controller", re);
+        public void onPrintersUpdated(List<PrinterInfo> printers) {
+            RemotePrintService service = mWeakService.get();
+            if (service != null) {
+                throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    service.mSpooler.onPrintersUpdated(printers);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
 
-        private void throwIfPrinterIdsForPrinterInfoTampered(
+        private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
                 List<PrinterInfo> printerInfos) {
             final int printerInfoCount = printerInfos.size();
             for (int i = 0; i < printerInfoCount; i++) {
                 PrinterId printerId = printerInfos.get(i).getId();
-                throwIfPrinterIdTampered(printerId);
+                throwIfPrinterIdTampered(serviceName, printerId);
             }
         }
 
-        private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
+        private void throwIfPrinterIdsTampered(ComponentName serviceName,
+                List<PrinterId> printerIds) {
             final int printerIdCount = printerIds.size();
             for (int i = 0; i < printerIdCount; i++) {
                 PrinterId printerId = printerIds.get(i);
-                throwIfPrinterIdTampered(printerId);
+                throwIfPrinterIdTampered(serviceName, printerId);
             }
         }
 
-        private void throwIfPrinterIdTampered(PrinterId printerId) {
+        private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
             if (printerId == null || printerId.getServiceName() == null
-                    || !printerId.getServiceName().equals(mComponentName)) {
+                    || !printerId.getServiceName().equals(serviceName)) {
                 throw new IllegalArgumentException("Invalid printer id: " + printerId);
             }
         }
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index c932e9b..d261288 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -32,9 +32,10 @@
 import android.print.IPrintSpooler;
 import android.print.IPrintSpoolerCallbacks;
 import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintAttributes;
 import android.print.PrintJobInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
@@ -92,7 +93,11 @@
     public static interface PrintSpoolerCallbacks {
         public void onPrintJobQueued(PrintJobInfo printJob);
         public void onAllPrintJobsForServiceHandled(ComponentName printService);
-        public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+        public void createPrinterDiscoverySession();
+        public void destroyPrinterDiscoverySession();
+        public void startPrinterDiscovery(List<PrinterId> priorityList);
+        public void stopPrinterDiscovery();
+        public void requestPrinterUpdate(PrinterId printerId);
     }
 
     public RemotePrintSpooler(Context context, int userId,
@@ -209,7 +214,7 @@
         return null;
     }
 
-    public final boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+    public final boolean setPrintJobState(int printJobId, int state, String error) {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
@@ -300,6 +305,78 @@
         }
     }
 
+    public final void onPrintersAdded(List<PrinterInfo> printers) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().onPrintersAdded(printers);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error adding printers.", re);
+        } catch (TimeoutException te) {
+            Slog.e(LOG_TAG, "Error adding printers.", te);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+                        + "] onPrintersAdded()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    public final void onPrintersRemoved(List<PrinterId> printerIds) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().onPrintersRemoved(printerIds);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error removing printers.", re);
+        } catch (TimeoutException te) {
+            Slog.e(LOG_TAG, "Error removing printers.", te);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+                        + "] onPrintersRemoved()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    public final void onPrintersUpdated(List<PrinterInfo> printers) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().onPrintersUpdated(printers);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error updating printers.", re);
+        } catch (TimeoutException te) {
+            Slog.e(LOG_TAG, "Error updating printers.", te);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+                        + "] onPrintersUpdted()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
         synchronized (mLock) {
             if (mRemoteInstance != null) {
@@ -488,7 +565,7 @@
         }
 
         public boolean setPrintJobState(IPrintSpooler target, int printJobId,
-                int status, CharSequence error) throws RemoteException, TimeoutException {
+                int status, String error) throws RemoteException, TimeoutException {
             final int sequence = onBeforeRemoteCall();
             target.setPrintJobState(printJobId, status, error, mCallback, sequence);
             return getResultTimed(sequence);
@@ -597,12 +674,64 @@
         }
 
         @Override
-        public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+        public void createPrinterDiscoverySession() {
             RemotePrintSpooler spooler = mWeakSpooler.get();
             if (spooler != null) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    spooler.mCallbacks.createPrinterDiscoverySession(observer);
+                    spooler.mCallbacks.createPrinterDiscoverySession();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void destroyPrinterDiscoverySession() {
+            RemotePrintSpooler spooler = mWeakSpooler.get();
+            if (spooler != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    spooler.mCallbacks.destroyPrinterDiscoverySession();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void startPrinterDiscovery(List<PrinterId> priorityList) {
+            RemotePrintSpooler spooler = mWeakSpooler.get();
+            if (spooler != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    spooler.mCallbacks.startPrinterDiscovery(priorityList);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void stopPrinterDiscovery() {
+            RemotePrintSpooler spooler = mWeakSpooler.get();
+            if (spooler != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    spooler.mCallbacks.stopPrinterDiscovery();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void requestPrinterUpdate(PrinterId printerId) {
+            RemotePrintSpooler spooler = mWeakSpooler.get();
+            if (spooler != null) {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    spooler.mCallbacks.requestPrinterUpdate(printerId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index ffcc9c3..9d7cfdd 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -21,8 +21,8 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.PrintJobInfo;
+import android.print.PrinterId;
 import android.printservice.PrintServiceInfo;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -105,7 +105,7 @@
     }
 
     @Override
-    public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+    public void createPrinterDiscoverySession() {
         final List<RemotePrintService> services;
         synchronized (mLock) {
             throwIfDestroyedLocked();
@@ -117,7 +117,73 @@
         final int serviceCount = services.size();
         for (int i = 0; i < serviceCount; i++) {
             RemotePrintService service = services.get(i);
-            service.createPrinterDiscoverySession(observer);
+            service.createPrinterDiscoverySession();
+        }
+    }
+
+    @Override
+    public void destroyPrinterDiscoverySession() {
+        final List<RemotePrintService> services;
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            if (mActiveServices.isEmpty()) {
+                return;
+            }
+            services = new ArrayList<RemotePrintService>(mActiveServices.values());
+        }
+        final int serviceCount = services.size();
+        for (int i = 0; i < serviceCount; i++) {
+            RemotePrintService service = services.get(i);
+            service.destroyPrinterDiscoverySession();
+        }
+    }
+
+    @Override
+    public void startPrinterDiscovery(List<PrinterId> printerIds) {
+        final List<RemotePrintService> services;
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            if (mActiveServices.isEmpty()) {
+                return;
+            }
+            services = new ArrayList<RemotePrintService>(mActiveServices.values());
+        }
+        final int serviceCount = services.size();
+        for (int i = 0; i < serviceCount; i++) {
+            RemotePrintService service = services.get(i);
+            service.startPrinterDiscovery(printerIds);
+        }
+    }
+
+    @Override
+    public void stopPrinterDiscovery() {
+        final List<RemotePrintService> services;
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            if (mActiveServices.isEmpty()) {
+                return;
+            }
+            services = new ArrayList<RemotePrintService>(mActiveServices.values());
+        }
+        final int serviceCount = services.size();
+        for (int i = 0; i < serviceCount; i++) {
+            RemotePrintService service = services.get(i);
+            service.stopPrinterDiscovery();
+        }
+    }
+
+    @Override
+    public void requestPrinterUpdate(PrinterId printerId) {
+        final RemotePrintService service;
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            if (mActiveServices.isEmpty()) {
+                return;
+            }
+            service = mActiveServices.get(printerId.getServiceName());
+        }
+        if (service != null) {
+            service.requestPrinterUpdate(printerId);
         }
     }