Merge "    First cut of the print notifications."
diff --git a/api/current.txt b/api/current.txt
index 402c2dc..18c6938 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18731,7 +18731,7 @@
 
   public final class PrinterId implements android.os.Parcelable {
     method public int describeContents();
-    method public java.lang.String getLocalId();
+    method public java.lang.String getPrinterName();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
@@ -18744,7 +18744,6 @@
     method public int getFittingModes();
     method public android.print.PrinterId getId();
     method public java.util.List<android.print.PrintAttributes.Tray> getInputTrays();
-    method public java.lang.CharSequence getLabel();
     method public java.util.List<android.print.PrintAttributes.MediaSize> getMediaSizes();
     method public android.print.PrintAttributes.Margins getMinMargins();
     method public int getOrientations();
@@ -18758,7 +18757,7 @@
   }
 
   public static final class PrinterInfo.Builder {
-    ctor public PrinterInfo.Builder(android.print.PrinterId, java.lang.CharSequence);
+    ctor public PrinterInfo.Builder(android.print.PrinterId);
     method public android.print.PrinterInfo.Builder addInputTray(android.print.PrintAttributes.Tray, boolean);
     method public android.print.PrinterInfo.Builder addMediaSize(android.print.PrintAttributes.MediaSize, boolean);
     method public android.print.PrinterInfo.Builder addOutputTray(android.print.PrintAttributes.Tray, boolean);
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 37ae2ca..7155096 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -33,4 +33,5 @@
             in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
             int appId, int userId);
     void cancelPrintJob(int printJobId, int appId, int userId);
+    void restartPrintJob(int printJobId, int appId, int userId);
 }
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index c55205d..428f972 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -40,10 +40,8 @@
     void createPrintJob(String printJobName, in IPrintClient client,
             in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
             IPrintSpoolerCallbacks callback, int appId, int sequence);
-    void cancelPrintJob(int printJobId, IPrintSpoolerCallbacks callback,
-            int appId, int sequence);
-    void setPrintJobState(int printJobId, int status, IPrintSpoolerCallbacks callback,
-            int sequence);
+    void setPrintJobState(int printJobId, int status, CharSequence error,
+            IPrintSpoolerCallbacks callback, int sequence);
     void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
             int sequence);
     void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 39546f3..096dcd5 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -124,6 +124,9 @@
     /** How many copies to print. */
     private int mCopies;
 
+    /** Failure reason if this job failed. */
+    private CharSequence mFailureReason;
+
     /** The pages to print */
     private PageRange[] mPageRanges;
 
@@ -148,6 +151,7 @@
         mUserId = other.mUserId;
         mTag = other.mTag;
         mCopies = other.mCopies;
+        mFailureReason = other.mFailureReason;
         mPageRanges = other.mPageRanges;
         mAttributes = other.mAttributes;
         mDocumentInfo = other.mDocumentInfo;
@@ -163,6 +167,9 @@
         mTag = parcel.readString();
         mCopies = parcel.readInt();
         if (parcel.readInt() == 1) {
+            mFailureReason = parcel.readCharSequence();
+        }
+        if (parcel.readInt() == 1) {
             Parcelable[] parcelables = parcel.readParcelableArray(null);
             mPageRanges = new PageRange[parcelables.length];
             for (int i = 0; i < parcelables.length; i++) {
@@ -345,6 +352,28 @@
     }
 
     /**
+     * The failure reason if this print job failed.
+     *
+     * @return The failure reason.
+     *
+     * @hide
+     */
+    public CharSequence getFailureReason() {
+        return mFailureReason;
+    }
+
+    /**
+     * The failure reason if this print job failed.
+     *
+     * @param failureReason The failure reason.
+     *
+     * @hide
+     */
+    public void setFailureReason(CharSequence failureReason) {
+        mFailureReason = failureReason;
+    }
+
+    /**
      * Gets the included pages.
      *
      * @return The included pages or <code>null</code> if not set.
@@ -421,6 +450,12 @@
         parcel.writeInt(mUserId);
         parcel.writeString(mTag);
         parcel.writeInt(mCopies);
+        if (mFailureReason != null) {
+            parcel.writeInt(1);
+            parcel.writeCharSequence(mFailureReason);
+        } else {
+            parcel.writeInt(0);
+        }
         if (mPageRanges != null) {
             parcel.writeInt(1);
             parcel.writeParcelableArray(mPageRanges, flags);
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index e27fbb2..8462736 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -26,26 +26,26 @@
  */
 public final class PrinterId implements Parcelable {
 
-    private final ComponentName mServiceComponentName;
+    private final ComponentName mServiceName;
 
-    private final String mLocalId;
+    private final String mPrinterName;
 
     /**
      * Creates a new instance.
      *
-     * @param serviceComponentName The managing print service.
-     * @param localId The unique id within the managing service.
+     * @param serviceName The managing print service.
+     * @param printerName The unique name within the managing service.
      *
      * @hide
      */
-    public PrinterId(ComponentName serviceComponentName, String localId) {
-        mServiceComponentName = serviceComponentName;
-        mLocalId = localId;
+    public PrinterId(ComponentName serviceName, String printerName) {
+        mServiceName = serviceName;
+        mPrinterName = printerName;
     }
 
     private PrinterId(Parcel parcel) {
-        mServiceComponentName = parcel.readParcelable(null);
-        mLocalId = parcel.readString();
+        mServiceName = parcel.readParcelable(null);
+        mPrinterName = parcel.readString();
     }
 
     /**
@@ -55,18 +55,18 @@
      *
      * @hide
      */
-    public ComponentName getService() {
-        return mServiceComponentName;
+    public ComponentName getServiceName() {
+        return mServiceName;
     }
 
     /**
-     * Gets the local id of this printer in the context
+     * Gets the name of this printer which is unique in the context
      * of the print service that manages it.
      *
-     * @return The local id.
+     * @return The printer name.
      */
-    public String getLocalId() {
-        return mLocalId;
+    public String getPrinterName() {
+        return mPrinterName;
     }
 
     @Override
@@ -76,8 +76,8 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeParcelable(mServiceComponentName, flags);
-        parcel.writeString(mLocalId);
+        parcel.writeParcelable(mServiceName, flags);
+        parcel.writeString(mPrinterName);
     }
 
     @Override
@@ -92,14 +92,14 @@
             return false;
         }
         PrinterId other = (PrinterId) object;
-        if (mServiceComponentName == null) {
-            if (other.mServiceComponentName != null) {
+        if (mServiceName == null) {
+            if (other.mServiceName != null) {
                 return false;
             }
-        } else if (!mServiceComponentName.equals(other.mServiceComponentName)) {
+        } else if (!mServiceName.equals(other.mServiceName)) {
             return false;
         }
-        if (!TextUtils.equals(mLocalId, other.mLocalId)) {
+        if (!TextUtils.equals(mPrinterName, other.mPrinterName)) {
             return false;
         }
         return true;
@@ -109,9 +109,9 @@
     public int hashCode() {
         final int prime = 31;
         int hashCode = 1;
-        hashCode = prime * hashCode + ((mServiceComponentName != null)
-                ? mServiceComponentName.hashCode() : 1);
-        hashCode = prime * hashCode + mLocalId.hashCode();
+        hashCode = prime * hashCode + ((mServiceName != null)
+                ? mServiceName.hashCode() : 1);
+        hashCode = prime * hashCode + mPrinterName.hashCode();
         return hashCode;
     }
 
@@ -119,9 +119,9 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("PrinterId{");
-        builder.append(mServiceComponentName.flattenToString());
+        builder.append(mServiceName.flattenToString());
         builder.append(":");
-        builder.append(mLocalId);
+        builder.append(mPrinterName);
         builder.append('}');
         return builder.toString();
     }
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 15bcb73..10cecca 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -22,7 +22,6 @@
 import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Resolution;
 import android.print.PrintAttributes.Tray;
-import android.text.TextUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -59,7 +58,6 @@
     // TODO: Add printer status constants.
 
     private PrinterId mId;
-    private CharSequence mLabel;
     private int mStatus;
 
     private Margins mMinMargins = DEFAULT_MARGINS;
@@ -92,7 +90,6 @@
      */
     public void copyFrom(PrinterInfo other) {
         mId = other.mId;
-        mLabel = other.mLabel;
         mStatus = other.mStatus;
 
         mMinMargins = other.mMinMargins;
@@ -162,15 +159,6 @@
     }
 
     /**
-     * Gets the human readable printer label.
-     *
-     * @return The human readable label.
-     */
-    public CharSequence getLabel() {
-        return mLabel;
-    }
-
-    /**
      * Gets the status of the printer.
      *
      * @return The status.
@@ -343,7 +331,6 @@
 
     private PrinterInfo(Parcel parcel) {
         mId = parcel.readParcelable(null);
-        mLabel = parcel.readCharSequence();
         mStatus = parcel.readInt();
 
         mMinMargins = readMargins(parcel);
@@ -369,7 +356,6 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mId, flags);
-        parcel.writeCharSequence(mLabel);
         parcel.writeInt(mStatus);
 
         writeMargins(mMinMargins, parcel);
@@ -392,7 +378,6 @@
         final int prime = 31;
         int result = 1;
         result = prime * result + ((mId == null) ? 0 : mId.hashCode());
-        result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
         result = prime * result + mStatus;
         result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
         result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
@@ -427,9 +412,6 @@
         } else if (!mId.equals(other.mId)) {
             return false;
         }
-        if (!TextUtils.equals(mLabel, other.mLabel)) {
-            return false;
-        }
         if (mStatus != other.mStatus) {
             return false;
         }
@@ -498,7 +480,6 @@
         StringBuilder builder = new StringBuilder();
         builder.append("PrinterInfo{");
         builder.append(mId).append(", \"");
-        builder.append(mLabel);
         builder.append("\"}");
         return builder.toString();
     }
@@ -639,20 +620,14 @@
          * Creates a new instance.
          *
          * @param printerId The printer id. Cannot be null.
-         * @param label The human readable printer label. Cannot be null or empty.
          *
          * @throws IllegalArgumentException If the printer id is null.
-         * @throws IllegalArgumentException If the label is empty.
          */
-        public Builder(PrinterId printerId, CharSequence label) {
+        public Builder(PrinterId printerId) {
             if (printerId == null) {
                 throw new IllegalArgumentException("printerId cannot be null.");
             }
-            if (TextUtils.isEmpty(label)) {
-                throw new IllegalArgumentException("label cannot be empty.");
-            }
             mPrototype = new PrinterInfo();
-            mPrototype.mLabel = label;
             mPrototype.mId = printerId;
         }
 
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index cdde4d8..f00b37c4 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -29,7 +29,7 @@
 interface IPrintServiceClient {
     List<PrintJobInfo> getPrintJobInfos();
     PrintJobInfo getPrintJobInfo(int printJobId);
-    boolean setPrintJobState(int printJobId, int status);
+    boolean setPrintJobState(int printJobId, int state, CharSequence error);
     boolean setPrintJobTag(int printJobId, String tag);
     oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
 }
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 0ac5a13..64c079e 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -121,7 +121,7 @@
      */
     public boolean start() {
         if (isQueued()) {
-            return setState(PrintJobInfo.STATE_STARTED);
+            return setState(PrintJobInfo.STATE_STARTED, null);
         }
         return false;
     }
@@ -136,41 +136,43 @@
      */
     public boolean complete() {
         if (isStarted()) {
-            return setState(PrintJobInfo.STATE_COMPLETED);
+            return setState(PrintJobInfo.STATE_COMPLETED, null);
         }
         return false;
     }
 
     /**
      * Fails the print job. You should call this method if {@link
-     * #isStarted()} returns true you filed while printing.
+     * #isQueued()} or {@link #isStarted()} returns true you failed
+     * while printing.
      *
-     * @param error The reason for the failure.
+     * @param error The human readable, short, and translated reason
+     * for the failure.
      * @return Whether the job was failed.
      *
+     * @see #isQueued()
      * @see #isStarted()
      */
     public boolean fail(CharSequence error) {
-        // TODO: Propagate the error message to the UI.
-        if (isStarted()) {
-            return setState(PrintJobInfo.STATE_FAILED);
+        if (isQueued() || isStarted()) {
+            return setState(PrintJobInfo.STATE_FAILED, error);
         }
         return false;
     }
 
     /**
      * Cancels the print job. You should call this method if {@link
-     * #isStarted()} returns true and you canceled the print job as a
-     * response to a call to {@link PrintService#onRequestCancelPrintJob(
-     * PrintJob)}.
+     * #isQueued()} or {@link #isStarted()} returns true and you canceled
+     * the print job as a response to a call to {@link PrintService
+     * #onRequestCancelPrintJob(PrintJob)}.
      *
      * @return Whether the job as canceled.
      *
      * @see #isStarted()
      */
     public boolean cancel() {
-        if (isStarted()) {
-            return setState(PrintJobInfo.STATE_CANCELED);
+        if (isQueued() || isStarted()) {
+            return setState(PrintJobInfo.STATE_CANCELED, null);
         }
         return false;
     }
@@ -222,13 +224,14 @@
                 || state == PrintJobInfo.STATE_CANCELED;
     }
 
-    private boolean setState(int state) {
+    private boolean setState(int state, CharSequence error) {
         try {
-            if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) {
+            if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
                 // Best effort - update the state of the cached info since
                 // we may not be able to re-fetch it later if the job gets
                 // removed from the spooler as a result of the state change.
                 mCachedInfo.setState(state);
+                mCachedInfo.setFailureReason(error);
                 return true;
             }
         } catch (RemoteException re) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 15e1b73..49384db 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -428,15 +428,14 @@
     }
 
     /**
-     * Generates a global printer id from a local id. The local id is unique
-     * only within this print service.
+     * Generates a global printer id given the printer's locally unique name.
      *
-     * @param localId The local id.
+     * @param printerName The printer name.
      * @return Global printer id.
      */
-    public final PrinterId generatePrinterId(String localId) {
+    public final PrinterId generatePrinterId(String printerName) {
         return new PrinterId(new ComponentName(getPackageName(),
-                getClass().getName()), localId);
+                getClass().getName()), printerName);
     }
 
     @Override
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 43dd1b6..128628d 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -113,23 +113,23 @@
 
                 String nodeName = parser.getName();
                 if (!TAG_PRINT_SERVICE.equals(nodeName)) {
-                    throw new XmlPullParserException(
-                            "Meta-data does not start with " + TAG_PRINT_SERVICE + " tag");
+                    Log.e(LOG_TAG, "Ignoring meta-data that does not start with "
+                            + TAG_PRINT_SERVICE + " tag");
+                } else {
+                    Resources resources = packageManager.getResourcesForApplication(
+                            resolveInfo.serviceInfo.applicationInfo);
+                    AttributeSet allAttributes = Xml.asAttributeSet(parser);
+                    TypedArray attributes = resources.obtainAttributes(allAttributes,
+                            com.android.internal.R.styleable.PrintService);
+
+                    settingsActivityName = attributes.getString(
+                            com.android.internal.R.styleable.PrintService_settingsActivity);
+
+                    addPrintersActivityName = attributes.getString(
+                            com.android.internal.R.styleable.PrintService_addPrintersActivity);
+
+                    attributes.recycle();
                 }
-
-                Resources resources = packageManager.getResourcesForApplication(
-                        resolveInfo.serviceInfo.applicationInfo);
-                AttributeSet allAttributes = Xml.asAttributeSet(parser);
-                TypedArray attributes = resources.obtainAttributes(allAttributes,
-                        com.android.internal.R.styleable.PrintService);
-
-                settingsActivityName = attributes.getString(
-                        com.android.internal.R.styleable.PrintService_settingsActivity);
-
-                addPrintersActivityName = attributes.getString(
-                        com.android.internal.R.styleable.PrintService_addPrintersActivity);
-
-                attributes.recycle();
             } catch (IOException ioe) {
                 Log.w(LOG_TAG, "Error reading meta-data:" + ioe);
             } catch (XmlPullParserException xppe) {
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index f0bbfa4..72d064a 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -25,8 +25,8 @@
 
     <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17"/>
 
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.ACCESS_ALL_PRINT_JOBS"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
     <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
         android:label="@string/permlab_bindPrintSpoolerService"
@@ -50,6 +50,11 @@
             android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar">
         </activity>
 
+        <receiver
+            android:name=".NotificationController$NotificationBroadcastReceiver"
+            android:exported="false" >
+        </receiver>
+
     </application>
 
 </manifest>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 1762693..f400f21 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -55,6 +55,28 @@
     <!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
     <string name="page_count_unknown">unknown</string>
 
+    <!-- Notifications -->
+
+    <!-- Template for the notificaiton label for a queued print job. [CHAR LIMIT=25] -->
+    <string name="queued_notification_title_template">Queued <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
+
+    <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
+    <string name="printing_notification_title_template">Printing <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
+
+    <!-- Template for the notificaiton label for a cancelling print job. [CHAR LIMIT=25] -->
+    <string name="cancelling_notification_title_template">Cancelling <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
+
+    <!-- Template for the notificaiton label for a failed print job. [CHAR LIMIT=25] -->
+    <string name="failed_notification_title_template">Printer error <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
+
+    <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
+    <string name="cancel">Cancel</string>
+
+    <!-- Label for the notification button for restrating a filed print job. [CHAR LIMIT=25] -->
+    <string name="restart">Restart</string>
+
+    <!-- Arrays -->
+
     <!-- Color mode labels. -->
     <string-array name="color_mode_labels">
         <!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
new file mode 100644
index 0000000..e4de4b8
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
@@ -0,0 +1,245 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.print.IPrintManager;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.util.Log;
+
+/**
+ * This class is responsible for updating the print notifications
+ * based on print job state transitions.
+ */
+public class NotificationController {
+    public static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
+
+    public static final String LOG_TAG = "NotificationController";
+
+    private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
+    private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
+    private static final String INTENT_EXTRA_PRINTJOB_ID = "INTENT_EXTRA_PRINTJOB_ID";
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+
+    public NotificationController(Context context) {
+        mContext = context;
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public void onPrintJobStateChanged(PrintJobInfo printJob, int oldState) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "onPrintJobStateChanged() printJobId: " + printJob.getId()
+                    + " oldState: " + PrintJobInfo.stateToString(oldState)
+                    + " newState:" + PrintJobInfo.stateToString(printJob.getState()));
+        }
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_QUEUED: {
+                createQueuingNotificaiton(printJob);
+            } break;
+
+            case PrintJobInfo.STATE_STARTED: {
+                createPrintingNotificaiton(printJob);
+            } break;
+
+            case PrintJobInfo.STATE_FAILED: {
+                createFailedNotificaiton(printJob);
+            } break;
+
+            case PrintJobInfo.STATE_COMPLETED:
+            case PrintJobInfo.STATE_CANCELED: {
+                removeNotification(printJob.getId());
+            } break;
+        }
+    }
+
+    private void createQueuingNotificaiton(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                // TODO: Use appropriate icon when assets are ready
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .setContentTitle(mContext.getString(R.string.queued_notification_title_template,
+                        printJob.getLabel()))
+                // TODO: Use appropriate icon when assets are ready
+                .addAction(android.R.drawable.ic_secure, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob.getId()))
+                .setContentText(printJob.getPrinterId().getPrinterName())
+                .setOngoing(true)
+                .setWhen(System.currentTimeMillis())
+                .setShowWhen(true);
+        mNotificationManager.notify(printJob.getId(), builder.build());
+    }
+
+    private void createPrintingNotificaiton(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                // TODO: Use appropriate icon when assets are ready
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .setContentTitle(mContext.getString(R.string.printing_notification_title_template,
+                        printJob.getLabel()))
+                // TODO: Use appropriate icon when assets are ready
+                .addAction(android.R.drawable.ic_secure, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob.getId()))
+                .setContentText(printJob.getPrinterId().getPrinterName())
+                .setOngoing(true)
+                .setWhen(System.currentTimeMillis())
+                .setShowWhen(true);
+        mNotificationManager.notify(printJob.getId(), builder.build());
+    }
+
+    private void createFailedNotificaiton(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                // TODO: Use appropriate icon when assets are ready
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .setContentTitle(mContext.getString(R.string.failed_notification_title_template,
+                        printJob.getLabel()))
+                // TODO: Use appropriate icon when assets are ready
+                .addAction(android.R.drawable.ic_secure, mContext.getString(R.string.cancel),
+                        createCancelIntent(printJob.getId()))
+                // TODO: Use appropriate icon when assets are ready
+                .addAction(android.R.drawable.ic_secure, mContext.getString(R.string.restart),
+                        createRestartIntent(printJob.getId()))
+                .setContentText(printJob.getFailureReason())
+                .setOngoing(true)
+                .setWhen(System.currentTimeMillis())
+                .setShowWhen(true);
+        mNotificationManager.notify(printJob.getId(), builder.build());
+    }
+
+    private void removeNotification(int printJobId) {
+        mNotificationManager.cancel(printJobId);
+    }
+
+    private PendingIntent createCancelIntent(int printJobId) {
+        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
+        intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + String.valueOf(printJobId));
+        intent.putExtra(INTENT_EXTRA_PRINTJOB_ID, printJobId);
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+    }
+
+    private PendingIntent createRestartIntent(int printJobId) {
+        Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
+        intent.setAction(INTENT_ACTION_RESTART_PRINTJOB + "_" + String.valueOf(printJobId));
+        intent.putExtra(INTENT_EXTRA_PRINTJOB_ID, printJobId);
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+    }
+
+    public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
+        private static final String LOG_TAG = "NotificationBroadcastReceiver";
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
+                final int printJobId = intent.getExtras().getInt(INTENT_EXTRA_PRINTJOB_ID);
+                handleCancelPrintJob(context, printJobId);
+            } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
+                final int printJobId = intent.getExtras().getInt(INTENT_EXTRA_PRINTJOB_ID);
+                handleRestartPrintJob(context, printJobId);
+            }
+        }
+
+        private void handleCancelPrintJob(final Context context, final int printJobId) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
+            }
+
+            PrintSpooler printSpooler = PrintSpooler.getInstance(context);
+
+            final PrintJobInfo printJob = printSpooler.getPrintJobInfo(printJobId,
+                    PrintManager.APP_ID_ANY);
+
+            if (printJob == null || printJob.getState() == PrintJobInfo.STATE_CANCELED) {
+                return;
+            }
+
+            // Put up a notification that we are trying to cancel.
+            NotificationManager notificationManager = (NotificationManager)
+                    context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+            Notification.Builder builder = new Notification.Builder(context)
+                    // TODO: Use appropriate icon when assets are ready
+                    .setSmallIcon(android.R.drawable.ic_secure)
+                    .setContentTitle(context.getString(
+                            R.string.cancelling_notification_title_template,
+                            printJob.getLabel()))
+                    .setContentText(printJob.getPrinterId().getPrinterName())
+                    .setOngoing(true)
+                    .setWhen(System.currentTimeMillis())
+                    .setShowWhen(true);
+
+            notificationManager.notify(printJob.getId(), builder.build());
+
+            // We need to request the cancellation to be done by the print
+            // manager service since it has to communicate with the managing
+            // print service to request the cancellation. Also we need the
+            // system service to be bound to the spooler since canceling a
+            // print job will trigger persistence of current jobs which is
+            // done on another thread and until it finishes the spooler has
+            // to be kept around.
+            IPrintManager printManager = IPrintManager.Stub.asInterface(
+                    ServiceManager.getService(Context.PRINT_SERVICE));
+
+            try {
+                printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
+                        UserHandle.myUserId());
+            } catch (RemoteException re) {
+                Log.i(LOG_TAG, "Error requestion print job cancellation", re);
+            }
+        }
+
+        private void handleRestartPrintJob(final Context context, final int printJobId) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "handleRestartPrintJob() printJobId:" + printJobId);
+            }
+
+            PrintSpooler printSpooler = PrintSpooler.getInstance(context);
+
+            PrintJobInfo printJob = printSpooler.getPrintJobInfo(printJobId,
+                    PrintManager.APP_ID_ANY);
+
+            if (printJob == null || printJob.getState() != PrintJobInfo.STATE_FAILED) {
+                return;
+            }
+
+            // We need to request the restart to be done by the print manager
+            // service since the latter must be bound to the spooler because
+            // restarting a print job will trigger persistence of current jobs
+            // which is done on another thread and until it finishes the spooler has
+            // to be kept around.
+            IPrintManager printManager = IPrintManager.Stub.asInterface(
+                    ServiceManager.getService(Context.PRINT_SERVICE));
+
+            try {
+                printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY,
+                        UserHandle.myUserId());
+            } catch (RemoteException re) {
+                Log.i(LOG_TAG, "Error requestion print job restart", re);
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index d61fd2c..484c8a9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -50,7 +50,6 @@
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.Choreographer;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -192,16 +191,16 @@
         mPrinterDiscoveryObserver = null;
         if (mController.isCancelled() || mController.isFailed()) {
             mSpooler.setPrintJobState(mPrintJobId,
-                    PrintJobInfo.STATE_CANCELED);
+                    PrintJobInfo.STATE_CANCELED, null);
         } else if (mController.hasStarted()) {
             mController.finish();
             if (mEditor.isPrintConfirmed()) {
                 if (mController.isFinished()) {
                     mSpooler.setPrintJobState(mPrintJobId,
-                            PrintJobInfo.STATE_QUEUED);
+                            PrintJobInfo.STATE_QUEUED, null);
                 } else {
                     mSpooler.setPrintJobState(mPrintJobId,
-                            PrintJobInfo.STATE_CANCELED);
+                            PrintJobInfo.STATE_CANCELED, null);
                 }
             }
         }
@@ -568,6 +567,10 @@
             @Override
             public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
                 if (spinner == mDestinationSpinner) {
+                    if (mIgnoreNextDestinationChange) {
+                        mIgnoreNextDestinationChange = false;
+                        return;
+                    }
                     mCurrPrintAttributes.clear();
                     SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
                     if (dstItem != null) {
@@ -590,12 +593,20 @@
                     }
                     updateUi();
                 } else if (spinner == mMediaSizeSpinner) {
+                    if (mIgnoreNextMediaSizeChange) {
+                        mIgnoreNextMediaSizeChange = false;
+                        return;
+                    }
                     SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
                     mCurrPrintAttributes.setMediaSize(mediaItem.value);
                     if (!hasErrors()) {
                         mController.update();
                     }
                 } else if (spinner == mColorModeSpinner) {
+                    if (mIgnoreNextColorModeChange) {
+                        mIgnoreNextColorModeChange = false;
+                        return;
+                    }
                     SpinnerItem<Integer> colorModeItem =
                             mColorModeSpinnerAdapter.getItem(position);
                     mCurrPrintAttributes.setColorMode(colorModeItem.value);
@@ -603,6 +614,10 @@
                         mController.update();
                     }
                 } else if (spinner == mOrientationSpinner) {
+                    if (mIgnoreNextOrientationChange) {
+                        mIgnoreNextOrientationChange = false;
+                        return;
+                    }
                     SpinnerItem<Integer> orientationItem =
                             mOrientationSpinnerAdapter.getItem(position);
                     mCurrPrintAttributes.setOrientation(orientationItem.value);
@@ -610,6 +625,10 @@
                         mController.update();
                     }
                 } else if (spinner == mRangeOptionsSpinner) {
+                    if (mIgnoreNextRangeOptionChange) {
+                        mIgnoreNextRangeOptionChange = false;
+                        return;
+                    }
                     updateUi();
                     if (!hasErrors()) {
                         mController.update();
@@ -636,6 +655,11 @@
 
             @Override
             public void afterTextChanged(Editable editable) {
+                if (mIgnoreNextCopiesChange) {
+                    mIgnoreNextCopiesChange = false;
+                    return;
+                }
+
                 final boolean hadErrors = hasErrors();
 
                 if (editable.length() == 0) {
@@ -674,6 +698,11 @@
 
             @Override
             public void afterTextChanged(Editable editable) {
+                if (mIgnoreNextRangeChange) {
+                    mIgnoreNextRangeChange = false;
+                    return;
+                }
+
                 final boolean hadErrors = hasErrors();
 
                 String text = editable.toString();
@@ -716,9 +745,19 @@
 
         private int mEditorState;
 
+        private boolean mIgnoreNextDestinationChange;
+        private boolean mIgnoreNextMediaSizeChange;
+        private boolean mIgnoreNextColorModeChange;
+        private boolean mIgnoreNextOrientationChange;
+        private boolean mIgnoreNextRangeOptionChange;
+        private boolean mIgnoreNextCopiesChange;
+        private boolean mIgnoreNextRangeChange;
+
         public Editor() {
             // Copies
             mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+            mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+            mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
             mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
             mCopiesEditText.selectAll();
 
@@ -772,19 +811,12 @@
                         rangeOptionsValues[i], rangeOptionsLabels[i]));
             }
             mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
-            mRangeOptionsSpinner.setSelection(0);
-            // Here is some voodoo to circumvent the weird behavior of AdapterView
-            // in which a selection listener may get a callback for an event that
-            // happened before the listener was registered. The reason for that is
-            // that the selection change is handled on the next layout pass.
-            Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
-                    new Runnable() {
-                @Override
-                public void run() {
-                    mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-                }
-            }, null, Choreographer.getFrameDelay() * 2);
+            if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                mIgnoreNextRangeOptionChange = true;
+                mRangeOptionsSpinner.setSelection(0);
+            }
 
+            // Preview button
             mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button);
             mPrintPreviewButton.setOnClickListener(new OnClickListener() {
                 @Override
@@ -797,6 +829,7 @@
                 }
             });
 
+            // Print button
             mPrintButton = (Button) findViewById(R.id.print_button);
             mPrintButton.setOnClickListener(new OnClickListener() {
                 @Override
@@ -806,11 +839,16 @@
                     mController.update();
                 }
             });
+
+            updateUi();
         }
 
         public void initialize() {
             mEditorState = EDITOR_STATE_INITIALIZED;
-            mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+            if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
+                mIgnoreNextDestinationChange = true;
+                mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+            }
         }
 
         public boolean isCancelled() {
@@ -900,35 +938,47 @@
                 // Destination
                 mDestinationSpinner.setEnabled(false);
 
-                mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher);
-                mCopiesEditText.setText(String.valueOf(MIN_COPIES));
-                mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
+                String minCopiesString = String.valueOf(MIN_COPIES);
+                if (!TextUtils.equals(mCopiesEditText.getText(), minCopiesString)) {
+                    mIgnoreNextCopiesChange = true;
+                    mCopiesEditText.setText(minCopiesString);
+                }
                 mCopiesEditText.setEnabled(false);
 
                 // Media size
-                mMediaSizeSpinner.setOnItemSelectedListener(null);
-                mMediaSizeSpinner.setSelection(AdapterView.INVALID_POSITION);
+                if (mMediaSizeSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
+                    mIgnoreNextMediaSizeChange = true;
+                    mMediaSizeSpinner.setSelection(AdapterView.INVALID_POSITION);
+                }
                 mMediaSizeSpinner.setEnabled(false);
 
                 // Color mode
-                mColorModeSpinner.setOnItemSelectedListener(null);
-                mColorModeSpinner.setSelection(AdapterView.INVALID_POSITION);
+                if (mColorModeSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
+                    mIgnoreNextColorModeChange = true;
+                    mColorModeSpinner.setSelection(AdapterView.INVALID_POSITION);
+                }
                 mColorModeSpinner.setEnabled(false);
 
                 // Orientation
-                mOrientationSpinner.setOnItemSelectedListener(null);
-                mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION);
+                if (mOrientationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
+                    mIgnoreNextOrientationChange = true;
+                    mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION);
+                }
                 mOrientationSpinner.setEnabled(false);
 
                 // Range
-                mRangeOptionsSpinner.setOnItemSelectedListener(null);
-                mRangeOptionsSpinner.setSelection(0);
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                    mIgnoreNextRangeOptionChange = true;
+                    mRangeOptionsSpinner.setSelection(0);
+                }
                 mRangeOptionsSpinner.setEnabled(false);
                 mRangeTitle.setText(getString(R.string.label_pages,
                         getString(R.string.page_count_unknown)));
-                mRangeEditText.removeTextChangedListener(mRangeTextWatcher);
-                mRangeEditText.setText("");
-                mRangeEditText.addTextChangedListener(mRangeTextWatcher);
+                if (!TextUtils.equals(mRangeEditText.getText(), "")) {
+                    mIgnoreNextRangeChange = true;
+                    mRangeEditText.setText("");
+                }
+
                 mRangeEditText.setEnabled(false);
                 mRangeEditText.setVisibility(View.INVISIBLE);
 
@@ -974,8 +1024,10 @@
                         mMediaSizeSpinner.setEnabled(true);
                         final int selectedMediaSizeIndex = Math.max(mediaSizes.indexOf(
                                 defaultAttributes.getMediaSize()), 0);
-                        mMediaSizeSpinner.setOnItemSelectedListener(null);
-                        mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
+                        if (mMediaSizeSpinner.getSelectedItemPosition() != selectedMediaSizeIndex) {
+                            mIgnoreNextMediaSizeChange = true;
+                            mMediaSizeSpinner.setSelection(selectedMediaSizeIndex);
+                        }
                     }
                 }
 
@@ -1014,8 +1066,10 @@
                         mColorModeSpinner.setEnabled(true);
                         final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
                                 (colorModes & defaultAttributes.getColorMode()));
-                        mColorModeSpinner.setOnItemSelectedListener(null);
-                        mColorModeSpinner.setSelection(selectedColorModeIndex);
+                        if (mColorModeSpinner.getSelectedItemPosition() != selectedColorModeIndex) {
+                            mIgnoreNextColorModeChange = true;
+                            mColorModeSpinner.setSelection(selectedColorModeIndex);
+                        }
                     }
                 }
 
@@ -1055,8 +1109,11 @@
                         mOrientationSpinner.setEnabled(true);
                         final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
                                 (orientations & defaultAttributes.getOrientation()));
-                        mOrientationSpinner.setOnItemSelectedListener(null);
-                        mOrientationSpinner.setSelection(selectedOrientationIndex);
+                        if (mOrientationSpinner.getSelectedItemPosition()
+                                != selectedOrientationIndex) {
+                            mIgnoreNextOrientationChange = true;
+                            mOrientationSpinner.setSelection(selectedOrientationIndex);
+                        }
                     }
                 }
 
@@ -1080,8 +1137,10 @@
                                     ? getString(R.string.page_count_unknown)
                                     : String.valueOf(pageCount)));
                 } else {
-                    mRangeOptionsSpinner.setOnItemSelectedListener(null);
-                    mRangeOptionsSpinner.setSelection(0);
+                    if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                        mIgnoreNextRangeOptionChange = true;
+                        mRangeOptionsSpinner.setSelection(0);
+                    }
                     mRangeOptionsSpinner.setEnabled(false);
                     mRangeTitle.setText(getString(R.string.label_pages,
                             getString(R.string.page_count_unknown)));
@@ -1109,26 +1168,12 @@
                 // Copies
                 if (mCopiesEditText.getError() == null
                         && TextUtils.isEmpty(mCopiesEditText.getText())) {
+                    mIgnoreNextCopiesChange = true;
                     mCopiesEditText.setText(String.valueOf(MIN_COPIES));
                     mCopiesEditText.selectAll();
                     mCopiesEditText.requestFocus();
                 }
             }
-
-            // Here is some voodoo to circumvent the weird behavior of AdapterView
-            // in which a selection listener may get a callback for an event that
-            // happened before the listener was registered. The reason for that is
-            // that the selection change is handled on the next layout pass.
-            Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
-                    new Runnable() {
-                @Override
-                public void run() {
-                    mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-                    mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-                    mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-                    mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-                }
-            }, null, Choreographer.getFrameDelay() * 2);
         }
 
         public void addPrinters(List<PrinterInfo> addedPrinters) {
@@ -1146,7 +1191,7 @@
                 }
                 if (!duplicate) {
                     mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>(
-                            addedPrinter, addedPrinter.getLabel()));
+                            addedPrinter, addedPrinter.getId().getPrinterName()));
                 } else {
                     Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
                 }
@@ -1269,14 +1314,14 @@
 
                 PrinterInfo printerInfo = getItem(position).value;
                 TextView title = (TextView) convertView.findViewById(R.id.title);
-                title.setText(printerInfo.getLabel());
+                title.setText(printerInfo.getId().getPrinterName());
 
                 try {
                     TextView subtitle = (TextView)
                             convertView.findViewById(R.id.subtitle);
                     PackageManager pm = getPackageManager();
                     PackageInfo packageInfo = pm.getPackageInfo(
-                            printerInfo.getId().getService().getPackageName(), 0);
+                            printerInfo.getId().getServiceName().getPackageName(), 0);
                     subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
                     subtitle.setVisibility(View.VISIBLE);
                 } catch (NameNotFoundException nnfe) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
index 870bfffd..fabd68f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -85,6 +85,8 @@
 
     private final PersistenceManager mPersistanceManager;
 
+    private final NotificationController mNotificationController;
+
     private final Handler mHandler;
 
     private final Context mContext;
@@ -103,6 +105,7 @@
     private PrintSpooler(Context context) {
         mContext = context;
         mPersistanceManager = new PersistenceManager(context);
+        mNotificationController = new NotificationController(context);
         mHandler = new MyHandler(context.getMainLooper());
     }
 
@@ -123,7 +126,7 @@
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = mClient;
             args.arg2 = printers;
-            mHandler.obtainMessage(MyHandler.MSG_REQUEST_UPDATE_PRINTERS,
+            mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
                     args).sendToTarget();
         }
     }
@@ -133,14 +136,14 @@
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = mClient;
             args.arg2 = observer;
-            mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+            mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
                     args).sendToTarget();
         }
     }
 
     public void stopPrinterDiscovery() {
         synchronized (mLock) {
-            mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY,
+            mHandler.obtainMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY,
                     mClient).sendToTarget();
         }
     }
@@ -154,7 +157,7 @@
                 PrinterId printerId = printJob.getPrinterId();
                 final boolean sameComponent = (componentName == null
                         || (printerId != null
-                        && componentName.equals(printerId.getService())));
+                        && componentName.equals(printerId.getServiceName())));
                 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
                         || printJob.getAppId() == appId;
                 final boolean sameState = (state == printJob.getState())
@@ -187,21 +190,6 @@
         }
     }
 
-    public boolean cancelPrintJob(int printJobId, int appId) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, appId);
-            if (printJob != null) {
-                switch (printJob.getState()) {
-                    case PrintJobInfo.STATE_CREATED:
-                    case PrintJobInfo.STATE_QUEUED: {
-                        setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED);
-                    } return true;
-                }
-            }
-            return false;
-        }
-    }
-
     public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client,
             PrintAttributes attributes, int appId) {
         synchronized (mLock) {
@@ -240,7 +228,7 @@
 
                     case PrintJobInfo.STATE_QUEUED:
                     case PrintJobInfo.STATE_STARTED: {
-                        ComponentName service = printJob.getPrinterId().getService();
+                        ComponentName service = printJob.getPrinterId().getServiceName();
                         List<PrintJobInfo> jobsPerService = activeJobsPerServiceMap.get(service);
                         if (jobsPerService == null) {
                             jobsPerService = new ArrayList<PrintJobInfo>();
@@ -250,7 +238,7 @@
                     } break;
 
                     default: {
-                        ComponentName service = printJob.getPrinterId().getService();
+                        ComponentName service = printJob.getPrinterId().getServiceName();
                         if (!activeJobsPerServiceMap.containsKey(service)) {
                             activeJobsPerServiceMap.put(service, null);
                         }
@@ -275,7 +263,7 @@
                         SomeArgs args = SomeArgs.obtain();
                         args.arg1 = client;
                         args.arg2 = new PrintJobInfo(printJob);
-                        mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+                        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
                                 args).sendToTarget();
                     }
                 }
@@ -283,13 +271,13 @@
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = client;
                 args.arg2 = service;
-                mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+                mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
                         args).sendToTarget();
             }
         }
 
         if (allPrintJobsHandled) {
-            mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+            mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
                     client).sendToTarget();
         }
     }
@@ -373,7 +361,7 @@
         }
     }
 
-    public boolean setPrintJobState(int printJobId, int state) {
+    public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
         boolean success = false;
 
         synchronized (mLock) {
@@ -382,9 +370,13 @@
             }
 
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null && printJob.getState() < state) {
+            if (printJob != null) {
                 success = true;
+
+                final int oldState = printJob.getState();
                 printJob.setState(state);
+                printJob.setFailureReason(error);
+                mNotificationController.onPrintJobStateChanged(printJob, oldState);
 
                 if (DEBUG_PRINT_JOB_LIFECYCLE) {
                     Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
@@ -405,18 +397,18 @@
                             return true;
                         }
 
-                        ComponentName service = printerId.getService();
+                        ComponentName service = printerId.getServiceName();
                         if (!hasActivePrintJobsForServiceLocked(service)) {
                             SomeArgs args = SomeArgs.obtain();
                             args.arg1 = mClient;
                             args.arg2 = service;
                             mHandler.obtainMessage(
-                                    MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+                                    MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
                                     args).sendToTarget();
                         }
 
                         if (!hasActivePrintJobsLocked()) {
-                            mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+                            mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
                                     mClient).sendToTarget();
                         }
                     } break;
@@ -425,7 +417,7 @@
                         SomeArgs args = SomeArgs.obtain();
                         args.arg1 = mClient;
                         args.arg2 = new PrintJobInfo(printJob);
-                        mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+                        mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
                                 args).sendToTarget();
                     } break;
                 }
@@ -455,7 +447,7 @@
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
             if (!isActiveState(printJob.getState())
-                    && printJob.getPrinterId().getService().equals(service)) {
+                    && printJob.getPrinterId().getServiceName().equals(service)) {
                 return true;
             }
         }
@@ -569,8 +561,8 @@
         private static final String ATTR_FITTING_MODE = "fittingMode";
         private static final String ATTR_ORIENTATION = "orientation";
 
-        private static final String ATTR_LOCAL_ID = "localId";
-        private static final String ATTR_SERVICE = "service";
+        private static final String ATTR_PRINTER_NAME = "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";
@@ -659,8 +651,8 @@
                     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, printerId.getService()
+                        serializer.attribute(null, ATTR_PRINTER_NAME, printerId.getPrinterName());
+                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
                                 .flattenToString());
                         serializer.endTag(null, TAG_PRINTER_ID);
                     }
@@ -811,17 +803,17 @@
                 parser.setInput(in, null);
                 parseState(parser);
             } catch (IllegalStateException ise) {
-                Slog.w(LOG_TAG, "Failed parsing " + ise);
+                Slog.w(LOG_TAG, "Failed parsing ", ise);
             } catch (NullPointerException npe) {
-                Slog.w(LOG_TAG, "Failed parsing " + npe);
+                Slog.w(LOG_TAG, "Failed parsing ", npe);
             } catch (NumberFormatException nfe) {
-                Slog.w(LOG_TAG, "Failed parsing " + nfe);
+                Slog.w(LOG_TAG, "Failed parsing ", nfe);
             } catch (XmlPullParserException xppe) {
-                Slog.w(LOG_TAG, "Failed parsing " + xppe);
+                Slog.w(LOG_TAG, "Failed parsing ", xppe);
             } catch (IOException ioe) {
-                Slog.w(LOG_TAG, "Failed parsing " + ioe);
+                Slog.w(LOG_TAG, "Failed parsing ", ioe);
             } catch (IndexOutOfBoundsException iobe) {
-                Slog.w(LOG_TAG, "Failed parsing " + iobe);
+                Slog.w(LOG_TAG, "Failed parsing ", iobe);
             } finally {
                 try {
                     in.close();
@@ -867,16 +859,16 @@
             printJob.setUserId(userId);
             String tag = parser.getAttributeValue(null, ATTR_TAG);
             printJob.setTag(tag);
-            String copies = parser.getAttributeValue(null, ATTR_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);
+                String localId = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
                 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
-                        null, ATTR_SERVICE));
+                        null, ATTR_SERVICE_NAME));
                 printJob.setPrinterId(new PrinterId(service, localId));
                 parser.next();
                 skipEmptyTextTags(parser);
@@ -1066,12 +1058,12 @@
     }
 
     private final class MyHandler extends Handler {
-        public static final int MSG_START_PRINTER_DISCOVERY = 1;
-        public static final int MSG_STOP_PRINTER_DISCOVERY = 2;
-        public static final int MSG_PRINT_JOB_QUEUED = 3;
-        public static final int MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
-        public static final int MSG_ALL_PRINT_JOBS_HANDLED = 5;
-        public static final int MSG_REQUEST_UPDATE_PRINTERS = 6;
+        public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
+        public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
+        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 5;
+        public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 6;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -1081,7 +1073,7 @@
         @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
             switch (message.what) {
-                case MSG_START_PRINTER_DISCOVERY: {
+                case MSG_ON_START_PRINTER_DISCOVERY: {
                     SomeArgs args = (SomeArgs) message.obj;
                     IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
                     IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg2;
@@ -1095,7 +1087,7 @@
                     }
                 } break;
 
-                case MSG_STOP_PRINTER_DISCOVERY: {
+                case MSG_ON_STOP_PRINTER_DISCOVERY: {
                     IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
                     if (client != null) {
                         try {
@@ -1106,7 +1098,7 @@
                     }
                 } break;
 
-                case MSG_PRINT_JOB_QUEUED: {
+                case MSG_ON_PRINT_JOB_QUEUED: {
                     SomeArgs args = (SomeArgs) message.obj;
                     IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
                     PrintJobInfo printJob = (PrintJobInfo) args.arg2;
@@ -1120,7 +1112,7 @@
                     }
                 } break;
 
-                case MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
+                case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
                     SomeArgs args = (SomeArgs) message.obj;
                     IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
                     ComponentName service = (ComponentName) args.arg2;
@@ -1135,7 +1127,7 @@
                     }
                 } break;
 
-                case MSG_ALL_PRINT_JOBS_HANDLED: {
+                case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
                     final IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
                     // 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
@@ -1154,7 +1146,7 @@
                     }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
                 } break;
 
-                case MSG_REQUEST_UPDATE_PRINTERS: {
+                case MSG_ON_REQUEST_UPDATE_PRINTERS: {
                     SomeArgs args = (SomeArgs) message.obj;
                     IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
                     List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 5ff2aa6..58853f7 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -91,17 +91,6 @@
                 }
             }
 
-            @Override
-            public void cancelPrintJob(int printJobId, IPrintSpoolerCallbacks callback,
-                    int appId, int sequence) throws RemoteException {
-                boolean success = false;
-                try {
-                    success = mSpooler.cancelPrintJob(printJobId, appId);
-                } finally {
-                    callback.onCancelPrintJobResult(success, sequence);
-                }
-            }
-
             @SuppressWarnings("deprecation")
             @Override
             public void createPrintJob(String printJobName, IPrintClient client,
@@ -135,14 +124,11 @@
             }
 
             @Override
-            public void setPrintJobState(int printJobId, int state,
-                    IPrintSpoolerCallbacks callback, int sequece)
-                            throws RemoteException {
+            public void setPrintJobState(int printJobId, int state, CharSequence error,
+                    IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
                 boolean success = false;
                 try {
-                    // TODO: Make sure the clients (print services) can set the state
-                    //       only to acceptable ones, e.g. not settings STATE_CREATED.
-                    success = mSpooler.setPrintJobState(printJobId, state);
+                    success = mSpooler.setPrintJobState(printJobId, state, error);
                 } finally {
                     callback.onSetPrintJobStateResult(success, sequece);
                 }
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index 86e7685..41399d8 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -144,22 +144,45 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            if (spooler.cancelPrintJob(printJobId, resolvedAppId)) {
-                return;
-            }
             PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
             if (printJobInfo == null) {
                 return;
             }
-            ComponentName printServiceName = printJobInfo.getPrinterId().getService();
-            RemotePrintService printService = null;
-            synchronized (mLock) {
-                printService = userState.getActiveServices().get(printServiceName);
+            if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
+                ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName();
+                RemotePrintService printService = null;
+                synchronized (mLock) {
+                    printService = userState.getActiveServices().get(printServiceName);
+                }
+                if (printService == null) {
+                    return;
+                }
+                printService.onRequestCancelPrintJob(printJobInfo);
+            } else {
+                // If the print job is failed we do not need cooperation
+                // from the print service.
+                spooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null);
             }
-            if (printService == null) {
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void restartPrintJob(int printJobId, int appId, int userId) {
+        final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
+        final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+        final RemotePrintSpooler spooler;
+        synchronized (mLock) {
+            spooler = getOrCreateUserStateLocked(resolvedUserId).getSpoolerLocked();
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId);
+            if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
                 return;
             }
-            printService.onRequestCancelPrintJob(printJobInfo);
+            spooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 7acf6ab..5aa9952 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -423,12 +423,12 @@
         }
 
         @Override
-        public boolean setPrintJobState(int printJobId, int state) {
+        public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
             RemotePrintService service = mWeakService.get();
             if (service != null) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return service.mSpooler.setPrintJobState(printJobId, state);
+                    return service.mSpooler.setPrintJobState(printJobId, state, error);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -524,8 +524,8 @@
         }
 
         private void throwIfPrinterIdTampered(PrinterId printerId) {
-            if (printerId == null || printerId.getService() == null
-                    || !printerId.getService().equals(mComponentName)) {
+            if (printerId == null || printerId.getServiceName() == null
+                    || !printerId.getServiceName().equals(mComponentName)) {
                 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 fe5b067..4e561bb 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -66,8 +66,6 @@
 
     private final CreatePrintJobCaller mCreatePrintJobCaller = new CreatePrintJobCaller();
 
-    private final CancelPrintJobCaller mCancelPrintJobCaller = new CancelPrintJobCaller();
-
     private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller();
 
     private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
@@ -163,31 +161,6 @@
         return null;
     }
 
-    public final boolean cancelPrintJob(int printJobId, int appId) {
-        throwIfCalledOnMainThread();
-        synchronized (mLock) {
-            throwIfDestroyedLocked();
-            mCanUnbind = false;
-        }
-        if (DEBUG) {
-            Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()");
-        }
-        try {
-            return mCancelPrintJobCaller.cancelPrintJob(getRemoteInstanceLazy(),
-                    printJobId, appId);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error canceling print job.", re);
-        } catch (TimeoutException te) {
-            Slog.e(LOG_TAG, "Error canceling print job.", te);
-        } finally {
-            synchronized (mLock) {
-                mCanUnbind = true;
-                mLock.notifyAll();
-            }
-        }
-        return false;
-    }
-
     public final void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
@@ -239,7 +212,7 @@
         return null;
     }
 
-    public final boolean setPrintJobState(int printJobId, int state) {
+    public final boolean setPrintJobState(int printJobId, int state, CharSequence error) {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
@@ -250,7 +223,7 @@
         }
         try {
             return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(),
-                    printJobId, state);
+                    printJobId, state, error);
         } catch (RemoteException re) {
             Slog.e(LOG_TAG, "Error setting print job state.", re);
         } catch (TimeoutException te) {
@@ -486,27 +459,6 @@
         }
     }
 
-    private static final class CancelPrintJobCaller extends TimedRemoteCaller<Boolean> {
-        private final IPrintSpoolerCallbacks mCallback;
-
-        public CancelPrintJobCaller() {
-            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
-            mCallback = new BasePrintSpoolerServiceCallbacks() {
-                @Override
-                public void onCancelPrintJobResult(boolean canceled, int sequence) {
-                    onRemoteMethodResult(canceled, sequence);
-                }
-            };
-        }
-
-        public boolean cancelPrintJob(IPrintSpooler target, int printJobId,
-                int appId) throws RemoteException, TimeoutException {
-            final int sequence = onBeforeRemoteCall();
-            target.cancelPrintJob(printJobId, mCallback, appId, sequence);
-            return getResultTimed(sequence);
-        }
-    }
-
     private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
         private final IPrintSpoolerCallbacks mCallback;
 
@@ -542,9 +494,9 @@
         }
 
         public boolean setPrintJobState(IPrintSpooler target, int printJobId,
-                int status) throws RemoteException, TimeoutException {
+                int status, CharSequence error) throws RemoteException, TimeoutException {
             final int sequence = onBeforeRemoteCall();
-            target.setPrintJobState(printJobId, status, mCallback, sequence);
+            target.setPrintJobState(printJobId, status, error, mCallback, sequence);
             return getResultTimed(sequence);
         }
     }
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index c41f9b0..c4fe124 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -85,7 +85,7 @@
         final RemotePrintService service;
         synchronized (mLock) {
             throwIfDestroyedLocked();
-            ComponentName printServiceName = printJob.getPrinterId().getService();
+            ComponentName printServiceName = printJob.getPrinterId().getServiceName();
             service = mActiveServices.get(printServiceName);
         }
         if (service != null) {
@@ -147,7 +147,7 @@
             if (mActiveServices.isEmpty()) {
                 return;
             }
-            service = mActiveServices.get(printerIds.get(0).getService());
+            service = mActiveServices.get(printerIds.get(0).getServiceName());
         }
         if (service != null) {
             service.onRequestUpdatePrinters(printerIds);