From 8c43376ea83a67414bd6823a472b76d41160239e Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Fri, 2 Aug 2013 14:22:19 -0700 Subject: [PATCH] First cut of the print notifications. 1. Added notifications for a queued print job, for a started print job, for ongoing canceling a print job, and for a failed print job. The notifications for queued and started state have a cancel action. The notification for failed print job has a cancel and a restart action. 2. Propagating failure message from the print service to the notifications. 3. PrintJobConfigActivity was not setting the initial value for the print job copies and was not updating the UI immediately after creation. 4. Refactored PrintJobConfigActivity to avoid using the hack to avoid reaction for item selection change in a spinner for an event that happened before the callback was registered. 5. Removed the label attribute from PrinterInfo and now PrinterId is composed of the printer name and the service component name. This is nice since for restarting print jobs we do not need to store information about the printer except the printer id which is already part of the PrintJobInfo's data. Also the printer name is not expected to change anyway. 6. Allowing cancellation of a queued print job. Also no print job is cancelled without asking the managing print service to do that. Before we were immediately canceling print jobs in queued state but it was possible for a buggy print service to not set the print job state to started before starting to do expensive work that will not be canceled. 7. PrintServiceInfo was throwing an exception the the meta-data XML for the print service was not well-formed which would crash the system process. Now we just ignore not well-formed meta-data. 8. Removed unused permissions from the PrintSpooler's manifest. Change-Id: Iba2dd14b487f56e137b90d1da17c3033422ab5e6 --- api/current.txt | 5 +- core/java/android/print/IPrintManager.aidl | 1 + core/java/android/print/IPrintSpooler.aidl | 6 +- core/java/android/print/PrintJobInfo.java | 35 +++ core/java/android/print/PrinterId.java | 52 ++-- core/java/android/print/PrinterInfo.java | 27 +- .../printservice/IPrintServiceClient.aidl | 2 +- core/java/android/printservice/PrintJob.java | 31 ++- .../android/printservice/PrintService.java | 9 +- .../printservice/PrintServiceInfo.java | 32 +-- packages/PrintSpooler/AndroidManifest.xml | 9 +- packages/PrintSpooler/res/values/strings.xml | 22 ++ .../printspooler/NotificationController.java | 245 ++++++++++++++++++ .../printspooler/PrintJobConfigActivity.java | 159 ++++++++---- .../android/printspooler/PrintSpooler.java | 104 ++++---- .../printspooler/PrintSpoolerService.java | 20 +- .../server/print/PrintManagerService.java | 41 ++- .../server/print/RemotePrintService.java | 8 +- .../server/print/RemotePrintSpooler.java | 56 +--- .../com/android/server/print/UserState.java | 4 +- 20 files changed, 574 insertions(+), 294 deletions(-) create mode 100644 packages/PrintSpooler/src/com/android/printspooler/NotificationController.java diff --git a/api/current.txt b/api/current.txt index cfcf7025192ca..59074d3de45b1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18641,7 +18641,7 @@ package android.print { 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; } @@ -18654,7 +18654,6 @@ package android.print { method public int getFittingModes(); method public android.print.PrinterId getId(); method public java.util.List getInputTrays(); - method public java.lang.CharSequence getLabel(); method public java.util.List getMediaSizes(); method public android.print.PrintAttributes.Margins getMinMargins(); method public int getOrientations(); @@ -18668,7 +18667,7 @@ package android.print { } 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 37ae2ca8b033b..71550960abcd5 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -33,4 +33,5 @@ interface IPrintManager { 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 c55205d0f1ff2..428f9722ec648 100644 --- a/core/java/android/print/IPrintSpooler.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -40,10 +40,8 @@ oneway interface IPrintSpooler { 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 39546f314247a..096dcd58a02cb 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -124,6 +124,9 @@ public final class PrintJobInfo implements Parcelable { /** 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 @@ public final class PrintJobInfo implements Parcelable { mUserId = other.mUserId; mTag = other.mTag; mCopies = other.mCopies; + mFailureReason = other.mFailureReason; mPageRanges = other.mPageRanges; mAttributes = other.mAttributes; mDocumentInfo = other.mDocumentInfo; @@ -162,6 +166,9 @@ public final class PrintJobInfo implements Parcelable { mUserId = parcel.readInt(); 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]; @@ -344,6 +351,28 @@ public final class PrintJobInfo implements Parcelable { mCopies = copyCount; } + /** + * 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. * @@ -421,6 +450,12 @@ public final class PrintJobInfo implements Parcelable { 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 e27fbb2360550..846273671f803 100644 --- a/core/java/android/print/PrinterId.java +++ b/core/java/android/print/PrinterId.java @@ -26,26 +26,26 @@ import android.text.TextUtils; */ 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 @@ public final class PrinterId implements Parcelable { * * @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 @@ public final class PrinterId implements Parcelable { @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 @@ public final class PrinterId implements Parcelable { 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 final class PrinterId implements Parcelable { 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 final class PrinterId implements Parcelable { 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 15bcb734b63eb..10cecca9e73f1 100644 --- a/core/java/android/print/PrinterInfo.java +++ b/core/java/android/print/PrinterInfo.java @@ -22,7 +22,6 @@ import android.print.PrintAttributes.Margins; 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 @@ public final class PrinterInfo implements Parcelable { // TODO: Add printer status constants. private PrinterId mId; - private CharSequence mLabel; private int mStatus; private Margins mMinMargins = DEFAULT_MARGINS; @@ -92,7 +90,6 @@ public final class PrinterInfo implements Parcelable { */ public void copyFrom(PrinterInfo other) { mId = other.mId; - mLabel = other.mLabel; mStatus = other.mStatus; mMinMargins = other.mMinMargins; @@ -161,15 +158,6 @@ public final class PrinterInfo implements Parcelable { return mId; } - /** - * Gets the human readable printer label. - * - * @return The human readable label. - */ - public CharSequence getLabel() { - return mLabel; - } - /** * Gets the status of the printer. * @@ -343,7 +331,6 @@ public final class PrinterInfo implements Parcelable { private PrinterInfo(Parcel parcel) { mId = parcel.readParcelable(null); - mLabel = parcel.readCharSequence(); mStatus = parcel.readInt(); mMinMargins = readMargins(parcel); @@ -369,7 +356,6 @@ public final class PrinterInfo implements Parcelable { @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 @@ public final class PrinterInfo implements Parcelable { 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 @@ public final class PrinterInfo implements Parcelable { } 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 @@ public final class PrinterInfo implements Parcelable { StringBuilder builder = new StringBuilder(); builder.append("PrinterInfo{"); builder.append(mId).append(", \""); - builder.append(mLabel); builder.append("\"}"); return builder.toString(); } @@ -639,20 +620,14 @@ public final class PrinterInfo implements Parcelable { * 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 cdde4d8651629..f00b37c4bf0f1 100644 --- a/core/java/android/printservice/IPrintServiceClient.aidl +++ b/core/java/android/printservice/IPrintServiceClient.aidl @@ -29,7 +29,7 @@ import android.print.PrinterInfo; interface IPrintServiceClient { List 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 0ac5a133b5d30..64c079e0055bf 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -121,7 +121,7 @@ public final class PrintJob { */ public boolean start() { if (isQueued()) { - return setState(PrintJobInfo.STATE_STARTED); + return setState(PrintJobInfo.STATE_STARTED, null); } return false; } @@ -136,41 +136,43 @@ public final class PrintJob { */ 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 @@ public final class PrintJob { || 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 15e1b7399719d..49384db2fabd1 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -428,15 +428,14 @@ public abstract class PrintService extends Service { } /** - * 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 43dd1b67de662..128628d530728 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -113,23 +113,23 @@ public final class PrintServiceInfo implements Parcelable { 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 f0bbfa49fe061..72d064a688521 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -25,8 +25,8 @@ - - + + + + + diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 176269364ef85..f400f2169c2da 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -55,6 +55,28 @@ unknown + + + + Queued %1$s + + + Printing %1$s + + + Cancelling %1$s + + + Printer error %1$s + + + Cancel + + + Restart + + + 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 0000000000000..e4de4b85273bf --- /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 d61fd2cadc5a3..484c8a96f3d1e 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.TextUtils.SimpleStringSplitter; 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 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { @Override public void onItemSelected(AdapterView spinner, View view, int position, long id) { if (spinner == mDestinationSpinner) { + if (mIgnoreNextDestinationChange) { + mIgnoreNextDestinationChange = false; + return; + } mCurrPrintAttributes.clear(); SpinnerItem dstItem = mDestinationSpinnerAdapter.getItem(position); if (dstItem != null) { @@ -590,12 +593,20 @@ public class PrintJobConfigActivity extends Activity { } updateUi(); } else if (spinner == mMediaSizeSpinner) { + if (mIgnoreNextMediaSizeChange) { + mIgnoreNextMediaSizeChange = false; + return; + } SpinnerItem mediaItem = mMediaSizeSpinnerAdapter.getItem(position); mCurrPrintAttributes.setMediaSize(mediaItem.value); if (!hasErrors()) { mController.update(); } } else if (spinner == mColorModeSpinner) { + if (mIgnoreNextColorModeChange) { + mIgnoreNextColorModeChange = false; + return; + } SpinnerItem colorModeItem = mColorModeSpinnerAdapter.getItem(position); mCurrPrintAttributes.setColorMode(colorModeItem.value); @@ -603,6 +614,10 @@ public class PrintJobConfigActivity extends Activity { mController.update(); } } else if (spinner == mOrientationSpinner) { + if (mIgnoreNextOrientationChange) { + mIgnoreNextOrientationChange = false; + return; + } SpinnerItem orientationItem = mOrientationSpinnerAdapter.getItem(position); mCurrPrintAttributes.setOrientation(orientationItem.value); @@ -610,6 +625,10 @@ public class PrintJobConfigActivity extends Activity { mController.update(); } } else if (spinner == mRangeOptionsSpinner) { + if (mIgnoreNextRangeOptionChange) { + mIgnoreNextRangeOptionChange = false; + return; + } updateUi(); if (!hasErrors()) { mController.update(); @@ -636,6 +655,11 @@ public class PrintJobConfigActivity extends Activity { @Override public void afterTextChanged(Editable editable) { + if (mIgnoreNextCopiesChange) { + mIgnoreNextCopiesChange = false; + return; + } + final boolean hadErrors = hasErrors(); if (editable.length() == 0) { @@ -674,6 +698,11 @@ public class PrintJobConfigActivity extends Activity { @Override public void afterTextChanged(Editable editable) { + if (mIgnoreNextRangeChange) { + mIgnoreNextRangeChange = false; + return; + } + final boolean hadErrors = hasErrors(); String text = editable.toString(); @@ -716,9 +745,19 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { } }); + // Print button mPrintButton = (Button) findViewById(R.id.print_button); mPrintButton.setOnClickListener(new OnClickListener() { @Override @@ -806,11 +839,16 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { // 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 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { 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 @@ public class PrintJobConfigActivity extends Activity { ? 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 @@ public class PrintJobConfigActivity extends Activity { // 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 addedPrinters) { @@ -1146,7 +1191,7 @@ public class PrintJobConfigActivity extends Activity { } if (!duplicate) { mDestinationSpinnerAdapter.add(new SpinnerItem( - addedPrinter, addedPrinter.getLabel())); + addedPrinter, addedPrinter.getId().getPrinterName())); } else { Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter); } @@ -1269,14 +1314,14 @@ public class PrintJobConfigActivity extends Activity { 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 870bfffda6216..fabd68f9683b8 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java @@ -85,6 +85,8 @@ public class PrintSpooler { private final PersistenceManager mPersistanceManager; + private final NotificationController mNotificationController; + private final Handler mHandler; private final Context mContext; @@ -103,6 +105,7 @@ public class PrintSpooler { private PrintSpooler(Context context) { mContext = context; mPersistanceManager = new PersistenceManager(context); + mNotificationController = new NotificationController(context); mHandler = new MyHandler(context.getMainLooper()); } @@ -123,7 +126,7 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 class PrintSpooler { } } - 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 @@ public class PrintSpooler { case PrintJobInfo.STATE_QUEUED: case PrintJobInfo.STATE_STARTED: { - ComponentName service = printJob.getPrinterId().getService(); + ComponentName service = printJob.getPrinterId().getServiceName(); List jobsPerService = activeJobsPerServiceMap.get(service); if (jobsPerService == null) { jobsPerService = new ArrayList(); @@ -250,7 +238,7 @@ public class PrintSpooler { } break; default: { - ComponentName service = printJob.getPrinterId().getService(); + ComponentName service = printJob.getPrinterId().getServiceName(); if (!activeJobsPerServiceMap.containsKey(service)) { activeJobsPerServiceMap.put(service, null); } @@ -275,7 +263,7 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 class PrintSpooler { } } - 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 @@ public class PrintSpooler { } 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { 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 @@ public class PrintSpooler { } 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 @@ public class PrintSpooler { @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 @@ public class PrintSpooler { } } 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 @@ public class PrintSpooler { } } 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 @@ public class PrintSpooler { } } 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 @@ public class PrintSpooler { } } 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 @@ public class PrintSpooler { }.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 printerIds = (List) args.arg2; diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 5ff2aa6100f3a..58853f79867e3 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -91,17 +91,6 @@ public final class PrintSpoolerService extends Service { } } - @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 @@ public final class PrintSpoolerService extends Service { } @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 86e76852b4d5d..41399d874d38d 100644 --- a/services/java/com/android/server/print/PrintManagerService.java +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -144,22 +144,45 @@ public final class PrintManagerService extends IPrintManager.Stub { } 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 7acf6ab11576c..5aa9952bd5365 100644 --- a/services/java/com/android/server/print/RemotePrintService.java +++ b/services/java/com/android/server/print/RemotePrintService.java @@ -423,12 +423,12 @@ final class RemotePrintService implements DeathRecipient { } @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 @@ final class RemotePrintService implements DeathRecipient { } 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 fe5b0675413ac..4e561bb2b42f2 100644 --- a/services/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/java/com/android/server/print/RemotePrintSpooler.java @@ -66,8 +66,6 @@ final class RemotePrintSpooler { 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 @@ final class RemotePrintSpooler { 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 @@ final class RemotePrintSpooler { 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 @@ final class RemotePrintSpooler { } 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 @@ final class RemotePrintSpooler { } } - private static final class CancelPrintJobCaller extends TimedRemoteCaller { - 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 { private final IPrintSpoolerCallbacks mCallback; @@ -542,9 +494,9 @@ final class RemotePrintSpooler { } 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 c41f9b02056fd..c4fe124adbfee 100644 --- a/services/java/com/android/server/print/UserState.java +++ b/services/java/com/android/server/print/UserState.java @@ -85,7 +85,7 @@ final class UserState implements PrintSpoolerCallbacks { 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 @@ final class UserState implements PrintSpoolerCallbacks { if (mActiveServices.isEmpty()) { return; } - service = mActiveServices.get(printerIds.get(0).getService()); + service = mActiveServices.get(printerIds.get(0).getServiceName()); } if (service != null) { service.onRequestUpdatePrinters(printerIds);