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
This commit is contained in:
Svetoslav Ganov
2013-08-02 14:22:19 -07:00
parent 55b409a97c
commit 8c43376ea8
20 changed files with 574 additions and 294 deletions

View File

@@ -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<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();
@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -29,7 +29,7 @@ import android.print.PrinterInfo;
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);
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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] -->

View File

@@ -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);
}
}
}
}

View File

@@ -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<PrinterInfo> 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<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 @@ public class PrintJobConfigActivity extends Activity {
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 @@ 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<PrinterInfo> addedPrinters) {
@@ -1146,7 +1191,7 @@ public class PrintJobConfigActivity extends Activity {
}
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 @@ 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) {

View File

@@ -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<PrintJobInfo> jobsPerService = activeJobsPerServiceMap.get(service);
if (jobsPerService == null) {
jobsPerService = new ArrayList<PrintJobInfo>();
@@ -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<PrinterId> printerIds = (List<PrinterId>) args.arg2;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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<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 @@ 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);
}
}

View File

@@ -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);