Merge "Iteration on the print sub-system."

This commit is contained in:
Svetoslav Ganov
2013-08-01 00:13:44 +00:00
committed by Android (Google) Code Review
25 changed files with 1800 additions and 1249 deletions

View File

@@ -18542,7 +18542,6 @@ package android.print {
method public void clear();
method public int describeContents();
method public int getColorMode();
method public int getCopies();
method public int getDuplexMode();
method public int getFittingMode();
method public android.print.PrintAttributes.Tray getInputTray();
@@ -18568,7 +18567,6 @@ package android.print {
ctor public PrintAttributes.Builder();
method public android.print.PrintAttributes create();
method public android.print.PrintAttributes.Builder setColorMode(int);
method public android.print.PrintAttributes.Builder setCopyCount(int);
method public android.print.PrintAttributes.Builder setDuplexMode(int);
method public android.print.PrintAttributes.Builder setFittingMode(int);
method public android.print.PrintAttributes.Builder setInputTray(android.print.PrintAttributes.Tray);
@@ -18654,18 +18652,20 @@ package android.print {
method public void onFinish();
method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle);
method public void onStart();
method public abstract void onWrite(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
method public abstract void onWrite(android.print.PageRange[], java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
field public static final java.lang.String METADATA_KEY_PRINT_PREVIEW = "KEY_METADATA_PRINT_PREVIEW";
}
public static abstract class PrintDocumentAdapter.LayoutResultCallback {
method public void onLayoutCancelled();
method public void onLayoutFailed(java.lang.CharSequence);
method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean);
}
public static abstract class PrintDocumentAdapter.WriteResultCallback {
method public void onWriteCancelled();
method public void onWriteFailed(java.lang.CharSequence);
method public void onWriteFinished(java.util.List<android.print.PageRange>);
method public void onWriteFinished(android.print.PageRange[]);
}
public final class PrintDocumentInfo implements android.os.Parcelable {
@@ -18696,6 +18696,7 @@ package android.print {
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public int getId();
method public java.lang.CharSequence getLabel();
method public android.print.PageRange[] getPages();

View File

@@ -34,8 +34,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Adapter for printing files.
@@ -69,7 +67,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
}
@Override
public void onWrite(List<PageRange> pages, FileDescriptor destination,
public void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback);
mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
@@ -127,9 +125,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
@Override
protected void onPostExecute(Void result) {
List<PageRange> pages = new ArrayList<PageRange>();
pages.add(PageRange.ALL_PAGES);
mResultCallback.onWriteFinished(pages);
mResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
}
@Override

View File

@@ -16,7 +16,6 @@
package android.print;
import android.os.ICancellationSignal;
import android.print.PrintDocumentInfo;
/**
@@ -25,7 +24,6 @@ import android.print.PrintDocumentInfo;
* @hide
*/
oneway interface ILayoutResultCallback {
void onLayoutStarted(ICancellationSignal cancellationSignal);
void onLayoutFinished(in PrintDocumentInfo info, boolean changed);
void onLayoutFailed(CharSequence error);
void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
void onLayoutFailed(CharSequence error, int sequence);
}

View File

@@ -31,8 +31,8 @@ import android.print.PrintAttributes;
oneway interface IPrintDocumentAdapter {
void start();
void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes,
ILayoutResultCallback callback, in Bundle metadata);
void write(in List<PageRange> pages, in ParcelFileDescriptor fd,
IWriteResultCallback callback);
ILayoutResultCallback callback, in Bundle metadata, int sequence);
void write(in PageRange[] pages, in ParcelFileDescriptor fd,
IWriteResultCallback callback, int sequence);
void finish();
}

View File

@@ -33,5 +33,4 @@ interface IPrintManager {
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
int appId, int userId);
void cancelPrintJob(int printJobId, int appId, int userId);
}

View File

@@ -28,9 +28,9 @@ import java.util.List;
*/
oneway interface IPrintSpoolerCallbacks {
void onGetPrintJobInfosResult(in List<PrintJobInfo> printJob, int sequence);
void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence);
void onCancelPrintJobResult(boolean canceled, int sequence);
void onSetPrintJobStateResult(boolean success, int sequence);
void onSetPrintJobTagResult(boolean success, int sequence);
void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.print;
import android.print.PrinterId;
import android.print.PrinterInfo;
/**
* Interface for observing the state of the print spooler.
*
* @hide
*/
oneway interface IPrinterDiscoveryObserver {
void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob);
void onAllPrintJobsHandled(in ComponentName printService);
void onAllPrintJobsHandled();
}

View File

@@ -16,7 +16,6 @@
package android.print;
import android.os.ICancellationSignal;
import android.print.PageRange;
/**
@@ -25,7 +24,6 @@ import android.print.PageRange;
* @hide
*/
oneway interface IWriteResultCallback {
void onWriteStarted(ICancellationSignal cancellationSignal);
void onWriteFinished(in List<PageRange> pages);
void onWriteFailed(CharSequence error);
void onWriteFinished(in PageRange[] pages, int sequence);
void onWriteFailed(CharSequence error, int sequence);
}

View File

@@ -92,9 +92,39 @@ public final class PageRange implements Parcelable {
parcel.writeInt(mEnd);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mEnd;
result = prime * result + mStart;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PageRange other = (PageRange) obj;
if (mEnd != other.mEnd) {
return false;
}
if (mStart != other.mStart) {
return false;
}
return true;
}
@Override
public String toString() {
if (this == ALL_PAGES) {
if (mStart == 0 && mEnd == Integer.MAX_VALUE) {
return "PageRange[<all pages>]";
}
StringBuilder builder = new StringBuilder();

View File

@@ -77,7 +77,6 @@ public final class PrintAttributes implements Parcelable {
private int mColorMode;
private int mFittingMode;
private int mOrientation;
private int mCopies;
PrintAttributes() {
/* hide constructor */
@@ -93,7 +92,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = parcel.readInt();
mFittingMode = parcel.readInt();
mOrientation = parcel.readInt();
mCopies = parcel.readInt();
}
/**
@@ -302,29 +300,6 @@ public final class PrintAttributes implements Parcelable {
mOrientation = orientation;
}
/**
* Gets the number of copies.
*
* @return The number of copies or zero if not set.
*/
public int getCopies() {
return mCopies;
}
/**
* Sets the number of copies.
*
* @param copyCount The number of copies.
*
* @hide
*/
public void setCopies(int copyCount) {
if (copyCount < 1) {
throw new IllegalArgumentException("Copies must be more than one.");
}
mCopies = copyCount;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
if (mMediaSize != null) {
@@ -361,7 +336,6 @@ public final class PrintAttributes implements Parcelable {
parcel.writeInt(mColorMode);
parcel.writeInt(mFittingMode);
parcel.writeInt(mOrientation);
parcel.writeInt(mCopies);
}
@Override
@@ -369,6 +343,101 @@ public final class PrintAttributes implements Parcelable {
return 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mColorMode;
result = prime * result + mDuplexMode;
result = prime * result + mFittingMode;
result = prime * result + mOrientation;
result = prime * result + ((mInputTray == null) ? 0 : mInputTray.hashCode());
result = prime * result + ((mMargins == null) ? 0 : mMargins.hashCode());
result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
result = prime * result + ((mOutputTray == null) ? 0 : mOutputTray.hashCode());
result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrintAttributes other = (PrintAttributes) obj;
if (mColorMode != other.mColorMode) {
return false;
}
if (mDuplexMode != other.mDuplexMode) {
return false;
}
if (mFittingMode != other.mFittingMode) {
return false;
}
if (mOrientation != other.mOrientation) {
return false;
}
if (mInputTray == null) {
if (other.mInputTray != null) {
return false;
}
} else if (!mInputTray.equals(other.mInputTray)) {
return false;
}
if (mOutputTray == null) {
if (other.mOutputTray != null) {
return false;
}
} else if (!mOutputTray.equals(other.mOutputTray)) {
return false;
}
if (mMargins == null) {
if (other.mMargins != null) {
return false;
}
} else if (!mMargins.equals(other.mMargins)) {
return false;
}
if (mMediaSize == null) {
if (other.mMediaSize != null) {
return false;
}
} else if (!mMediaSize.equals(other.mMediaSize)) {
return false;
}
if (mResolution == null) {
if (other.mResolution != null) {
return false;
}
} else if (!mResolution.equals(other.mResolution)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintAttributes{");
builder.append("mediaSize: ").append(mMediaSize);
builder.append(", resolution: ").append(mResolution);
builder.append(", margins: ").append(mMargins);
builder.append(", inputTray: ").append(mInputTray);
builder.append(", outputTray: ").append(mOutputTray);
builder.append(", colorMode: ").append(colorModeToString(mColorMode));
builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
builder.append(", orientation: ").append(orientationToString(mOrientation));
builder.append("}");
return builder.toString();
}
/** hide */
public void clear() {
mMediaSize = null;
@@ -380,7 +449,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = 0;
mFittingMode = 0;
mOrientation = 0;
mCopies = 0;
}
/**
@@ -396,7 +464,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = other.mColorMode;
mFittingMode = other.mFittingMode;
mOrientation = other.mOrientation;
mCopies = other.mCopies;
}
/**
@@ -953,6 +1020,44 @@ public final class PrintAttributes implements Parcelable {
parcel.readInt());
}
@Override
public int hashCode() {
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 + mWidthMils;
result = prime * result + mHeightMils;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MediaSize other = (MediaSize) obj;
if (!TextUtils.equals(mId, other.mId)) {
return false;
}
if (!TextUtils.equals(mLabel, other.mLabel)) {
return false;
}
if (mWidthMils != other.mWidthMils) {
return false;
}
if (mHeightMils != other.mHeightMils) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -1060,6 +1165,44 @@ public final class PrintAttributes implements Parcelable {
parcel.readInt());
}
@Override
public int hashCode() {
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 + mHorizontalDpi;
result = prime * result + mVerticalDpi;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Resolution other = (Resolution) obj;
if (!TextUtils.equals(mId, other.mId)) {
return false;
}
if (!TextUtils.equals(mLabel, other.mLabel)) {
return false;
}
if (mHorizontalDpi != other.mHorizontalDpi) {
return false;
}
if (mVerticalDpi != other.mVerticalDpi) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -1165,6 +1308,44 @@ public final class PrintAttributes implements Parcelable {
parcel.readInt());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mBottomMils;
result = prime * result + mLeftMils;
result = prime * result + mRightMils;
result = prime * result + mTopMils;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Margins other = (Margins) obj;
if (mBottomMils != other.mBottomMils) {
return false;
}
if (mLeftMils != other.mLeftMils) {
return false;
}
if (mRightMils != other.mRightMils) {
return false;
}
if (mTopMils != other.mTopMils) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -1234,6 +1415,36 @@ public final class PrintAttributes implements Parcelable {
parcel.readCharSequence());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mId == null) ? 0 : mId.hashCode());
result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Tray other = (Tray) obj;
if (!TextUtils.equals(mId, other.mId)) {
return false;
}
if (!TextUtils.equals(mLabel, other.mLabel)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -1246,21 +1457,6 @@ public final class PrintAttributes implements Parcelable {
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintAttributes{");
builder.append("mediaSize: ").append(mMediaSize);
builder.append(", resolution: ").append(mResolution);
builder.append(", margins: ").append(mMargins);
builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
builder.append(", colorMode: ").append(colorModeToString(mColorMode));
builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
builder.append(", orientation: ").append(orientationToString(mOrientation));
builder.append(", copies: ").append(mCopies);
return builder.toString();
}
private static String duplexModeToString(int duplexMode) {
switch (duplexMode) {
case DUPLEX_MODE_NONE: {
@@ -1412,7 +1608,7 @@ public final class PrintAttributes implements Parcelable {
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
*/
public Builder setDuplexMode(int duplexMode) {
if (Integer.bitCount(duplexMode) != 1) {
if (Integer.bitCount(duplexMode) > 1) {
throw new IllegalArgumentException("can specify at most one duplexMode bit.");
}
mAttributes.setDuplexMode(duplexMode);
@@ -1470,17 +1666,6 @@ public final class PrintAttributes implements Parcelable {
return this;
}
/**
* Sets the number of copies.
*
* @param copyCount A greater or equal to zero copy count.
* @return This builder.
*/
public Builder setCopyCount(int copyCount) {
mAttributes.setCopies(copyCount);
return this;
}
/**
* Creates a new {@link PrintAttributes} instance.
*

View File

@@ -41,7 +41,7 @@ import java.util.List;
* <li>
* After every call to {@link #onLayout(PrintAttributes, PrintAttributes,
* CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to
* {@link #onWrite(List, FileDescriptor, CancellationSignal, WriteResultCallback)}
* {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)}
* asking you to write a PDF file with the content for specific pages.
* </li>
* <li>
@@ -64,7 +64,7 @@ import java.util.List;
* PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on
* the UI thread (assuming onStart initializes resources needed for layout).
* This will ensure that the UI does not change while you are laying out the
* printed content. Then you can handle {@link #onWrite(List, FileDescriptor,
* printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor,
* CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another
* thread. This will ensure that the UI is frozen for the minimal amount of
* time. Also this assumes that you will generate the printed content in
@@ -141,7 +141,7 @@ public abstract class PrintDocumentAdapter {
* made on the main thread.
* </p>
*
* @param pages The pages whose content to print.
* @param pages The pages whose content to print - non-overlapping in ascending order.
* @param destination The destination file descriptor to which to write.
* @param cancellationSignal Signal for observing cancel writing requests.
* @param callback Callback to inform the system for the write result.
@@ -149,7 +149,7 @@ public abstract class PrintDocumentAdapter {
* @see WriteResultCallback
* @see CancellationSignal
*/
public abstract void onWrite(List<PageRange> pages, FileDescriptor destination,
public abstract void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback);
/**
@@ -163,7 +163,7 @@ public abstract class PrintDocumentAdapter {
/**
* Base class for implementing a callback for the result of {@link
* PrintDocumentAdapter#onWrite(List, FileDescriptor, CancellationSignal,
* PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal,
* WriteResultCallback)}.
*/
public static abstract class WriteResultCallback {
@@ -178,9 +178,9 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that all the data was written.
*
* @param pages The pages that were written.
* @param pages The pages that were written. Cannot be null or empty.
*/
public void onWriteFinished(List<PageRange> pages) {
public void onWriteFinished(PageRange[] pages) {
/* do nothing - stub */
}
@@ -192,6 +192,13 @@ public abstract class PrintDocumentAdapter {
public void onWriteFailed(CharSequence error) {
/* do nothing - stub */
}
/**
* Notifies that write was cancelled as a result of a cancellation request.
*/
public void onWriteCancelled() {
/* do nothing - stub */
}
}
/**
@@ -211,7 +218,7 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that the layout finished and whether the content changed.
*
* @param info An info object describing the document.
* @param info An info object describing the document. Cannot be null.
* @param changed Whether the layout changed.
*
* @see PrintDocumentInfo
@@ -228,5 +235,12 @@ public abstract class PrintDocumentAdapter {
public void onLayoutFailed(CharSequence error) {
/* do nothing - stub */
}
/**
* Notifies that layout was cancelled as a result of a cancellation request.
*/
public void onLayoutCancelled() {
/* do nothing - stub */
}
}
}

View File

@@ -110,6 +110,36 @@ public final class PrintDocumentInfo implements Parcelable {
parcel.writeInt(mContentType);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mContentType;
result = prime * result + mPageCount;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PrintDocumentInfo other = (PrintDocumentInfo) obj;
if (mContentType != other.mContentType) {
return false;
}
if (mPageCount != other.mPageCount) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

View File

@@ -55,6 +55,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
if (isInImmutableState()) {
return mCachedInfo;
}
PrintJobInfo info = mPrintManager.getPrintJobInfo(mId);
if (info != null) {
mCachedInfo = info;
@@ -66,7 +69,15 @@ public final class PrintJob {
* Cancels this print job.
*/
public void cancel() {
mPrintManager.cancelPrintJob(mId);
if (!isInImmutableState()) {
mPrintManager.cancelPrintJob(mId);
}
}
private boolean isInImmutableState() {
final int state = mCachedInfo.getState();
return state == PrintJobInfo.STATE_COMPLETED
|| state == PrintJobInfo.STATE_CANCELED;
}
@Override

View File

@@ -19,6 +19,8 @@ package android.print;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/**
* This class represents the description of a print job.
*/
@@ -119,6 +121,9 @@ public final class PrintJobInfo implements Parcelable {
/** Optional tag assigned by a print service.*/
private String mTag;
/** How many copies to print. */
private int mCopies;
/** The pages to print */
private PageRange[] mPageRanges;
@@ -142,6 +147,8 @@ public final class PrintJobInfo implements Parcelable {
mAppId = other.mAppId;
mUserId = other.mUserId;
mTag = other.mTag;
mCopies = other.mCopies;
mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
}
@@ -154,8 +161,13 @@ public final class PrintJobInfo implements Parcelable {
mAppId = parcel.readInt();
mUserId = parcel.readInt();
mTag = parcel.readString();
mCopies = parcel.readInt();
if (parcel.readInt() == 1) {
mPageRanges = (PageRange[]) parcel.readParcelableArray(null);
Parcelable[] parcelables = parcel.readParcelableArray(null);
mPageRanges = new PageRange[parcelables.length];
for (int i = 0; i < parcelables.length; i++) {
mPageRanges[i] = (PageRange) parcelables[i];
}
}
if (parcel.readInt() == 1) {
mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel);
@@ -309,6 +321,29 @@ public final class PrintJobInfo implements Parcelable {
mTag = tag;
}
/**
* Gets the number of copies.
*
* @return The number of copies or zero if not set.
*/
public int getCopies() {
return mCopies;
}
/**
* Sets the number of copies.
*
* @param copyCount The number of copies.
*
* @hide
*/
public void setCopies(int copyCount) {
if (copyCount < 1) {
throw new IllegalArgumentException("Copies must be more than one.");
}
mCopies = copyCount;
}
/**
* Gets the included pages.
*
@@ -385,6 +420,7 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeInt(mAppId);
parcel.writeInt(mUserId);
parcel.writeString(mTag);
parcel.writeInt(mCopies);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
@@ -413,10 +449,14 @@ public final class PrintJobInfo implements Parcelable {
builder.append(", id: ").append(mId);
builder.append(", status: ").append(stateToString(mState));
builder.append(", printer: " + mPrinterId);
builder.append(", tag: ").append(mTag);
builder.append(", copies: ").append(mCopies);
builder.append(", attributes: " + (mAttributes != null
? mAttributes.toString() : null));
builder.append(", documentInfo: " + (mDocumentInfo != null
? mDocumentInfo.toString() : null));
builder.append(", pages: " + (mPageRanges != null
? Arrays.toString(mPageRanges) : null));
builder.append("}");
return builder.toString();
}

View File

@@ -22,7 +22,6 @@ import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -223,6 +222,11 @@ public final class PrintManager {
}
private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
private final Object mLock = new Object();
private CancellationSignal mLayoutOrWriteCancellation;
private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
private Handler mHandler; // Strong reference OK - cleared in finish()
@@ -239,22 +243,36 @@ public final class PrintManager {
@Override
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
ILayoutResultCallback callback, Bundle metadata) {
ILayoutResultCallback callback, Bundle metadata, int sequence) {
synchronized (mLock) {
if (mLayoutOrWriteCancellation != null) {
mLayoutOrWriteCancellation.cancel();
}
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = oldAttributes;
args.arg2 = newAttributes;
args.arg3 = callback;
args.arg4 = metadata;
args.argi1 = sequence;
mHandler.removeMessages(MyHandler.MSG_LAYOUT);
mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
}
@Override
public void write(List<PageRange> pages, ParcelFileDescriptor fd,
IWriteResultCallback callback) {
public void write(PageRange[] pages, ParcelFileDescriptor fd,
IWriteResultCallback callback, int sequence) {
synchronized (mLock) {
if (mLayoutOrWriteCancellation != null) {
mLayoutOrWriteCancellation.cancel();
}
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = pages;
args.arg2 = fd.getFileDescriptor();
args.arg3 = callback;
args.argi1 = sequence;
mHandler.removeMessages(MyHandler.MSG_WRITE);
mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
}
@@ -283,7 +301,6 @@ public final class PrintManager {
}
@Override
@SuppressWarnings("unchecked")
public void handleMessage(Message message) {
if (isFinished()) {
return;
@@ -295,42 +312,116 @@ public final class PrintManager {
case MSG_LAYOUT: {
SomeArgs args = (SomeArgs) message.obj;
PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
PrintAttributes newAttributes = (PrintAttributes) args.arg2;
ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
Bundle metadata = (Bundle) args.arg4;
final PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
final PrintAttributes newAttributes = (PrintAttributes) args.arg2;
final ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
final Bundle metadata = (Bundle) args.arg4;
final int sequence = args.argi1;
args.recycle();
try {
ICancellationSignal remoteSignal = CancellationSignal.createTransport();
callback.onLayoutStarted(remoteSignal);
mDocumentAdapter.onLayout(oldAttributes, newAttributes,
CancellationSignal.fromTransport(remoteSignal),
new LayoutResultCallbackWrapper(callback), metadata);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error printing", re);
CancellationSignal cancellation = new CancellationSignal();
synchronized (mLock) {
mLayoutOrWriteCancellation = cancellation;
}
mDocumentAdapter.onLayout(oldAttributes, newAttributes,
cancellation, new LayoutResultCallback() {
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
if (info == null) {
throw new IllegalArgumentException("info cannot be null");
}
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
try {
callback.onLayoutFinished(info, changed, sequence);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
}
}
@Override
public void onLayoutFailed(CharSequence error) {
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
try {
callback.onLayoutFailed(error, sequence);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
}
}
@Override
public void onLayoutCancelled() {
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
}
}, metadata);
} break;
case MSG_WRITE: {
SomeArgs args = (SomeArgs) message.obj;
List<PageRange> pages = (List<PageRange>) args.arg1;
FileDescriptor fd = (FileDescriptor) args.arg2;
IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
final PageRange[] pages = (PageRange[]) args.arg1;
final FileDescriptor fd = (FileDescriptor) args.arg2;
final IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
final int sequence = args.argi1;
args.recycle();
try {
ICancellationSignal remoteSignal = CancellationSignal.createTransport();
callback.onWriteStarted(remoteSignal);
mDocumentAdapter.onWrite(pages, fd,
CancellationSignal.fromTransport(remoteSignal),
new WriteResultCallbackWrapper(callback, fd));
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error printing", re);
IoUtils.closeQuietly(fd);
CancellationSignal cancellation = new CancellationSignal();
synchronized (mLock) {
mLayoutOrWriteCancellation = cancellation;
}
mDocumentAdapter.onWrite(pages, fd, cancellation,
new WriteResultCallback() {
@Override
public void onWriteFinished(PageRange[] pages) {
if (pages == null) {
throw new IllegalArgumentException("pages cannot be null");
}
if (pages.length == 0) {
throw new IllegalArgumentException("pages cannot be empty");
}
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
// Close before notifying the other end. We want
// to be ready by the time we announce it.
IoUtils.closeQuietly(fd);
try {
callback.onWriteFinished(pages, sequence);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFinished", re);
}
}
@Override
public void onWriteFailed(CharSequence error) {
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
// Close before notifying the other end. We want
// to be ready by the time we announce it.
IoUtils.closeQuietly(fd);
try {
callback.onWriteFailed(error, sequence);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFailed", re);
}
}
@Override
public void onWriteCancelled() {
synchronized (mLock) {
mLayoutOrWriteCancellation = null;
}
// Just close the fd for now.
IoUtils.closeQuietly(fd);
}
});
} break;
case MSG_FINISH: {
@@ -346,67 +437,4 @@ public final class PrintManager {
}
}
}
private static final class WriteResultCallbackWrapper extends WriteResultCallback {
private final IWriteResultCallback mWrappedCallback;
private final FileDescriptor mFd;
public WriteResultCallbackWrapper(IWriteResultCallback callback,
FileDescriptor fd) {
mWrappedCallback = callback;
mFd = fd;
}
@Override
public void onWriteFinished(List<PageRange> pages) {
try {
// Close before notifying the other end. We want
// to be ready by the time we announce it.
IoUtils.closeQuietly(mFd);
mWrappedCallback.onWriteFinished(pages);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFinished", re);
}
}
@Override
public void onWriteFailed(CharSequence error) {
try {
// Close before notifying the other end. We want
// to be ready by the time we announce it.
IoUtils.closeQuietly(mFd);
mWrappedCallback.onWriteFailed(error);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onWriteFailed", re);
}
}
}
private static final class LayoutResultCallbackWrapper extends LayoutResultCallback {
private final ILayoutResultCallback mWrappedCallback;
public LayoutResultCallbackWrapper(ILayoutResultCallback callback) {
mWrappedCallback = callback;
}
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
try {
mWrappedCallback.onLayoutFinished(info, changed);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
}
}
@Override
public void onLayoutFailed(CharSequence error) {
try {
mWrappedCallback.onLayoutFailed(error);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
}
}
}
}

View File

@@ -41,8 +41,6 @@ public final class PrinterInfo implements Parcelable {
*/
public static final int DEFAULT_UNDEFINED = -1;
private static final int MIN_COPIES = 1;
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_INPUT_TRAY = 2;
@@ -240,9 +238,6 @@ public final class PrinterInfo implements Parcelable {
public void getDefaults(PrintAttributes outAttributes) {
outAttributes.clear();
// TODO: Do we want a printer to specify default copies?
outAttributes.setCopies(MIN_COPIES);
outAttributes.setMargins(mDefaultMargins);
final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE);

View File

@@ -61,6 +61,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
if (isInImmutableState()) {
return mCachedInfo;
}
PrintJobInfo info = null;
try {
info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId());
@@ -182,6 +185,9 @@ public final class PrintJob {
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
if (isInImmutableState()) {
return false;
}
try {
return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag);
} catch (RemoteException re) {
@@ -210,6 +216,12 @@ public final class PrintJob {
return mCachedInfo.getId();
}
private boolean isInImmutableState() {
final int state = mCachedInfo.getState();
return state == PrintJobInfo.STATE_COMPLETED
|| state == PrintJobInfo.STATE_CANCELED;
}
private boolean setState(int state) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) {

View File

@@ -34,12 +34,13 @@
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:layout_marginLeft="32dip"
android:layout_marginTop="32dip"
android:layout_marginRight="32dip"
android:layout_marginBottom="12dip"
android:layout_row="0"
android:layout_column="0"
android:layout_columnSpan="2"
android:minHeight="?android:attr/listPreferredItemHeightSmall">
android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<!-- Copies -->
@@ -57,7 +58,8 @@
android:layout_gravity="bottom"
android:inputType="numberDecimal"
android:selectAllOnFocus="true"
android:minWidth="150dip">
android:minWidth="150dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
@@ -86,7 +88,8 @@
android:layout_marginBottom="12dip"
android:layout_row="2"
android:layout_column="1"
android:minWidth="150dip">
android:minWidth="150dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -114,7 +117,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="0"
android:minWidth="150dip">
android:minWidth="150dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -142,7 +146,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="1"
android:minWidth="150dip">
android:minWidth="150dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -169,7 +174,8 @@
android:layout_marginRight="12dip"
android:layout_row="6"
android:layout_column="0"
android:minWidth="150dip">
android:minWidth="150dip"
android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<view
@@ -186,10 +192,12 @@
android:minWidth="150dip"
android:hint="@string/pages_range_example"
android:inputType="textNoSuggestions"
android:visibility="gone">
android:visibility="gone"
android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
android:id="@+id/page_range_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="32dip"
@@ -231,7 +239,8 @@
android:layout_columnSpan="2"
android:text="@string/print_preview"
android:gravity="left|center_vertical"
android:background="?android:attr/selectableItemBackground">
android:background="?android:attr/selectableItemBackground"
android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
<ImageView
@@ -269,7 +278,8 @@
android:layout_columnSpan="2"
android:padding="0dip"
android:text="@string/print_button"
android:background="?android:attr/selectableItemBackground">
android:background="?android:attr/selectableItemBackground"
android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
</GridLayout>

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
<string name="app_label">Print Spooler</string>
@@ -38,7 +38,7 @@
<string name="label_orientation">ORIENTATION</string>
<!-- Label of the page selection widget. [CHAR LIMIT=20] -->
<string name="label_pages">PAGES</string>
<string name="label_pages">PAGES (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
<!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=15] -->
<string name="pages_range_example">e.g. 1&#8211;5, 8</string>
@@ -52,6 +52,9 @@
<!-- Title of the message that the printing application crashed. [CHAR LIMIT=50] -->
<string name="printing_app_crashed">Printing app crashed</string>
<!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
<string name="page_count_unknown">unknown</string>
<!-- 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

@@ -19,6 +19,9 @@ package com.android.printspooler;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintClient;
@@ -39,6 +42,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import libcore.io.IoUtils;
@@ -59,9 +63,9 @@ import java.util.Map;
public class PrintSpooler {
private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
private static final String LOG_TAG = "PrintSpooler";
private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
private static final boolean DEBUG_PERSISTENCE = true;
@@ -81,6 +85,8 @@ public class PrintSpooler {
private final PersistenceManager mPersistanceManager;
private final Handler mHandler;
private final Context mContext;
public IPrintSpoolerClient mClient;
@@ -97,6 +103,7 @@ public class PrintSpooler {
private PrintSpooler(Context context) {
mContext = context;
mPersistanceManager = new PersistenceManager(context);
mHandler = new MyHandler(context.getMainLooper());
}
public void setCleint(IPrintSpoolerClient client) {
@@ -112,36 +119,25 @@ public class PrintSpooler {
}
public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
IPrintSpoolerClient client = null;
synchronized (mLock) {
client = mClient;
}
if (client != null) {
try {
client.onStartPrinterDiscovery(observer);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = mClient;
args.arg2 = observer;
mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
args).sendToTarget();
}
}
public void stopPrinterDiscovery() {
IPrintSpoolerClient client = null;
synchronized (mLock) {
client = mClient;
}
if (client != null) {
try {
client.onStopPrinterDiscovery();
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
}
mHandler.obtainMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY,
mClient).sendToTarget();
}
}
public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, int appId) {
List<PrintJobInfo> foundPrintJobs = null;
synchronized (mLock) {
List<PrintJobInfo> foundPrintJobs = null;
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
@@ -162,8 +158,8 @@ public class PrintSpooler {
foundPrintJobs.add(printJob);
}
}
return foundPrintJobs;
}
return foundPrintJobs;
}
public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
@@ -172,11 +168,12 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
if (printJob.getId() == printJobId
&& (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
&& (appId == PrintManager.APP_ID_ANY
|| appId == printJob.getAppId())) {
return printJob;
}
}
return null;
return null;
}
}
@@ -217,7 +214,7 @@ public class PrintSpooler {
Map<ComponentName, List<PrintJobInfo>> activeJobsPerServiceMap =
new HashMap<ComponentName, List<PrintJobInfo>>();
synchronized(mLock) {
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
@@ -265,16 +262,25 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobs.get(i);
if (printJob.getState() == PrintJobInfo.STATE_QUEUED) {
callOnPrintJobQueuedQuietly(client, printJob);
SomeArgs args = SomeArgs.obtain();
args.arg1 = client;
args.arg2 = new PrintJobInfo(printJob);
mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
args).sendToTarget();
}
}
} else {
callOnAllPrintJobsForServiceHandledQuietly(client, service);
SomeArgs args = SomeArgs.obtain();
args.arg1 = client;
args.arg2 = service;
mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
args).sendToTarget();
}
}
if (allPrintJobsHandled) {
callOnAllPrintJobsHandledQuietly(client);
mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
client).sendToTarget();
}
}
@@ -297,37 +303,43 @@ public class PrintSpooler {
return false;
}
@SuppressWarnings("resource")
public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
final PrintJobInfo printJob;
synchronized (mLock) {
FileInputStream in = null;
FileOutputStream out = null;
try {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
File file = generateFileForPrintJob(printJobId);
in = new FileInputStream(file);
out = new FileOutputStream(fd.getFileDescriptor());
printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
}
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
FileInputStream in = null;
FileOutputStream out = null;
try {
if (printJob != null) {
File file = generateFileForPrintJob(printJobId);
in = new FileInputStream(file);
out = new FileOutputStream(fd.getFileDescriptor());
}
final byte[] buffer = new byte[8192];
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
return true;
return null;
}
out.write(buffer, 0, readByteCount);
}
} catch (FileNotFoundException fnfe) {
Log.e(LOG_TAG, "Error writing print job data!", fnfe);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error writing print job data!", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(fd);
}
} catch (FileNotFoundException fnfe) {
Log.e(LOG_TAG, "Error writing print job data!", fnfe);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error writing print job data!", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(fd);
Log.i(LOG_TAG, "[END WRITE]");
return null;
}
}
return false;
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
public File generateFileForPrintJob(int printJobId) {
@@ -354,28 +366,24 @@ public class PrintSpooler {
public boolean setPrintJobState(int printJobId, int state) {
boolean success = false;
boolean allPrintJobsHandled = false;
boolean allPrintJobsForServiceHandled = false;
IPrintSpoolerClient client = null;
PrintJobInfo queuedPrintJob = null;
PrintJobInfo removedPrintJob = null;
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
client = mClient;
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null && printJob.getState() < state) {
success = true;
printJob.setState(state);
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
}
// TODO: Update notifications.
switch (state) {
case PrintJobInfo.STATE_COMPLETED:
case PrintJobInfo.STATE_CANCELED: {
removedPrintJob = printJob;
removePrintJobLocked(printJob);
// No printer means creation of a print job was cancelled,
@@ -387,83 +395,46 @@ public class PrintSpooler {
return true;
}
allPrintJobsHandled = !hasActivePrintJobsLocked();
allPrintJobsForServiceHandled = !hasActivePrintJobsForServiceLocked(
printerId.getService());
ComponentName service = printerId.getService();
if (!hasActivePrintJobsForServiceLocked(service)) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = mClient;
args.arg2 = service;
mHandler.obtainMessage(
MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
args).sendToTarget();
}
if (!hasActivePrintJobsLocked()) {
mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
mClient).sendToTarget();
}
} break;
case PrintJobInfo.STATE_QUEUED: {
queuedPrintJob = new PrintJobInfo(printJob);
SomeArgs args = SomeArgs.obtain();
args.arg1 = mClient;
args.arg2 = new PrintJobInfo(printJob);
mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
args).sendToTarget();
} break;
}
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
if (shouldPersistPrintJob(printJob)) {
mPersistanceManager.writeStateLocked();
}
mPersistanceManager.writeStateLocked();
}
}
if (queuedPrintJob != null) {
callOnPrintJobQueuedQuietly(client, queuedPrintJob);
}
if (allPrintJobsForServiceHandled) {
callOnAllPrintJobsForServiceHandledQuietly(client,
removedPrintJob.getPrinterId().getService());
}
if (allPrintJobsHandled) {
callOnAllPrintJobsHandledQuietly(client);
}
return success;
}
private void callOnPrintJobQueuedQuietly(IPrintSpoolerClient client,
PrintJobInfo printJob) {
try {
client.onPrintJobQueued(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
}
}
private void callOnAllPrintJobsForServiceHandledQuietly(IPrintSpoolerClient client,
ComponentName service) {
try {
client.onAllPrintJobsForServiceHandled(service);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
}
}
private void callOnAllPrintJobsHandledQuietly(final IPrintSpoolerClient client) {
// This has to run on the tread that is persisting the current state
// since this call may result in the system unbinding from the spooler
// and as a result the spooler process may get killed before the write
// completes.
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
client.onAllPrintJobsHandled();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
private boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
switch (printJob.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED: {
return true;
}
if (!isActiveState(printJob.getState())) {
return true;
}
}
return false;
@@ -473,72 +444,89 @@ public class PrintSpooler {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
switch (printJob.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED: {
if (printJob.getPrinterId().getService().equals(service)) {
return true;
}
} break;
if (!isActiveState(printJob.getState())
&& printJob.getPrinterId().getService().equals(service)) {
return true;
}
}
return false;
}
private static boolean isActiveState(int printJobState) {
return printJobState != PrintJobInfo.STATE_CREATED
|| printJobState != PrintJobInfo.STATE_QUEUED
|| printJobState != PrintJobInfo.STATE_STARTED;
}
public boolean setPrintJobTag(int printJobId, String tag) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
String printJobTag = printJob.getTag();
if (printJobTag == null) {
if (tag == null) {
return false;
}
} else if (printJobTag.equals(tag)) {
return false;
}
printJob.setTag(tag);
mPersistanceManager.writeStateLocked();
if (shouldPersistPrintJob(printJob)) {
mPersistanceManager.writeStateLocked();
}
return true;
}
}
return false;
}
public final boolean setPrintJobPrintDocumentInfo(int printJobId, PrintDocumentInfo info) {
public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setCopies(copies);
}
}
}
public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setDocumentInfo(info);
mPersistanceManager.writeStateLocked();
return true;
}
}
return false;
}
public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setAttributes(attributes);
mPersistanceManager.writeStateLocked();
}
}
}
public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
public void setPrintJobPrinterIdNoPersistence(int printJobId, PrinterId printerId) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPrinterId(printerId);
mPersistanceManager.writeStateLocked();
}
}
}
public boolean setPrintJobPages(int printJobId, PageRange[] pages) {
public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPages(pages);
mPersistanceManager.writeStateLocked();
return true;
}
}
return false;
}
private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
}
private final class PersistenceManager {
@@ -558,6 +546,7 @@ public class PrintSpooler {
private static final String ATTR_APP_ID = "appId";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_TAG = "tag";
private static final String ATTR_COPIES = "copies";
private static final String TAG_MEDIA_SIZE = "mediaSize";
private static final String TAG_RESOLUTION = "resolution";
@@ -620,6 +609,9 @@ public class PrintSpooler {
}
private void doWriteStateLocked() {
if (DEBUG_PERSISTENCE) {
Log.i(LOG_TAG, "[PERSIST START]");
}
FileOutputStream out = null;
try {
out = mStatePersistFile.startWrite();
@@ -652,6 +644,7 @@ public class PrintSpooler {
if (tag != null) {
serializer.attribute(null, ATTR_TAG, tag);
}
serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
@@ -775,6 +768,9 @@ public class PrintSpooler {
serializer.endTag(null, TAG_SPOOLER);
serializer.endDocument();
mStatePersistFile.finishWrite(out);
if (DEBUG_PERSISTENCE) {
Log.i(LOG_TAG, "[PERSIST END]");
}
} catch (IOException e) {
Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
mStatePersistFile.failWrite(out);
@@ -861,6 +857,8 @@ public class PrintSpooler {
printJob.setUserId(userId);
String tag = parser.getAttributeValue(null, ATTR_TAG);
printJob.setTag(tag);
String copies = parser.getAttributeValue(null, ATTR_TAG);
printJob.setCopies(Integer.parseInt(copies));
parser.next();
@@ -892,7 +890,9 @@ public class PrintSpooler {
parser.next();
}
if (pageRanges != null) {
printJob.setPages((PageRange[]) pageRanges.toArray());
PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
pageRanges.toArray(pageRangesArray);
printJob.setPages(pageRangesArray);
}
skipEmptyTextTags(parser);
@@ -1054,4 +1054,93 @@ public class PrintSpooler {
return true;
}
}
private final class MyHandler extends Handler {
public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
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 MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_ON_START_PRINTER_DISCOVERY: {
SomeArgs args = (SomeArgs) message.obj;
IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg2;
args.recycle();
if (client != null) {
try {
client.onStartPrinterDiscovery(observer);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
}
}
} break;
case MSG_ON_STOP_PRINTER_DISCOVERY: {
IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
if (client != null) {
try {
client.onStopPrinterDiscovery();
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
}
}
} break;
case MSG_ON_PRINT_JOB_QUEUED: {
SomeArgs args = (SomeArgs) message.obj;
IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
PrintJobInfo printJob = (PrintJobInfo) args.arg2;
args.recycle();
if (client != null) {
try {
client.onPrintJobQueued(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
}
}
} break;
case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
SomeArgs args = (SomeArgs) message.obj;
IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
ComponentName service = (ComponentName) args.arg2;
args.recycle();
if (client != null) {
try {
client.onAllPrintJobsForServiceHandled(service);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
}
}
} break;
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
// and as a result the spooler process may get killed before the write
// completes.
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
client.onAllPrintJobsHandled();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
} break;
}
}
}
}

View File

@@ -114,12 +114,11 @@ public final class PrintSpoolerService extends Service {
attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
printAdapter.asBinder());
intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
printJob.getId());
intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_ATTRIBUTES, attributes);
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT

View File

@@ -17,8 +17,8 @@
package com.android.printspooler;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.ILayoutResultCallback;
@@ -26,11 +26,7 @@ import android.print.IPrintDocumentAdapter;
import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter.LayoutResultCallback;
import android.print.PrintDocumentAdapter.WriteResultCallback;
import android.print.PrintDocumentInfo;
import android.util.Log;
import android.util.Slog;
import libcore.io.IoUtils;
@@ -40,8 +36,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents a remote print document adapter instance.
@@ -49,461 +43,99 @@ import java.util.List;
final class RemotePrintDocumentAdapter {
private static final String LOG_TAG = "RemotePrintDocumentAdapter";
private static final boolean DEBUG = true;
public static final int STATE_INITIALIZED = 0;
public static final int STATE_START_COMPLETED = 1;
public static final int STATE_LAYOUT_STARTED = 2;
public static final int STATE_LAYOUT_COMPLETED = 3;
public static final int STATE_WRITE_STARTED = 4;
public static final int STATE_WRITE_COMPLETED = 5;
public static final int STATE_FINISH_COMPLETED = 6;
public static final int STATE_FAILED = 7;
private final Object mLock = new Object();
private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>();
private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final IPrintDocumentAdapter mRemoteInterface;
private final File mFile;
private int mState = STATE_INITIALIZED;
public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
mRemoteInterface = printAdatper;
mFile = file;
}
public File getFile() {
public void start() {
if (DEBUG) {
Log.i(LOG_TAG, "getFile()");
Log.i(LOG_TAG, "start()");
}
synchronized (mLock) {
if (mState != STATE_WRITE_COMPLETED
&& mState != STATE_FINISH_COMPLETED) {
throw new IllegalStateException("Write not completed");
}
return mFile;
try {
mRemoteInterface.start();
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling start()", re);
}
}
public void start() {
QueuedAsyncTask task = new QueuedAsyncTask() {
@Override
protected Void doInBackground(Void... params) {
if (DEBUG) {
Log.i(LOG_TAG, "start()");
}
synchronized (mLock) {
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("Invalid state: " + mState);
}
}
try {
mRemoteInterface.start();
synchronized (mLock) {
mState = STATE_START_COMPLETED;
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error reading file", re);
}
return null;
}
@Override
public void cancel() {
/* cannot be cancelled */
}
};
synchronized (mLock) {
mTaskQueue.add(task);
}
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
LayoutResultCallback callback, Bundle metadata) {
LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback,
metadata);
synchronized (mLock) {
mTaskQueue.add(task);
ILayoutResultCallback callback, Bundle metadata, int sequence) {
if (DEBUG) {
Log.i(LOG_TAG, "layout()");
}
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
public void write(List<PageRange> pages, WriteResultCallback callback) {
WriteAsyncTask task = new WriteAsyncTask(pages, callback);
synchronized (mLock) {
mTaskQueue.add(task);
}
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
public void cancel() {
synchronized (mLock) {
final int taskCount = mTaskQueue.size();
for (int i = taskCount - 1; i >= 0; i--) {
mTaskQueue.remove(i).cancel();
}
try {
mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling layout()", re);
}
}
public void finish() {
QueuedAsyncTask task = new QueuedAsyncTask() {
public void write(final PageRange[] pages, final IWriteResultCallback callback,
final int sequence) {
if (DEBUG) {
Log.i(LOG_TAG, "write()");
}
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (DEBUG) {
Log.i(LOG_TAG, "finish()");
}
synchronized (mLock) {
if (mState < STATE_START_COMPLETED) {
return null;
}
}
InputStream in = null;
OutputStream out = null;
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
try {
mRemoteInterface.finish();
synchronized (mLock) {
mState = STATE_FINISH_COMPLETED;
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
source = pipe[0];
sink = pipe[1];
in = new FileInputStream(source.getFileDescriptor());
out = new FileOutputStream(mFile);
// Async call to initiate the other process writing the data.
mRemoteInterface.write(pages, sink, callback, sequence);
// Close the source. It is now held by the client.
sink.close();
sink = null;
// Read the data.
final byte[] buffer = new byte[8192];
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
}
out.write(buffer, 0, readByteCount);
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error reading file", re);
mState = STATE_FAILED;
Log.e(LOG_TAG, "Error calling write()", re);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error calling write()", ioe);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(sink);
IoUtils.closeQuietly(source);
}
return null;
}
@Override
public void cancel() {
/* cannot be cancelled */
}
};
synchronized (mLock) {
mTaskQueue.add(task);
}
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> {
public void cancel() {
super.cancel(true);
public void finish() {
if (DEBUG) {
Log.i(LOG_TAG, "finish()");
}
}
private final class LayoutAsyncTask extends QueuedAsyncTask {
private final PrintAttributes mOldAttributes;
private final PrintAttributes mNewAttributes;
private final LayoutResultCallback mCallback;
private final Bundle mMetadata;
private final ILayoutResultCallback mILayoutResultCallback =
new ILayoutResultCallback.Stub() {
@Override
public void onLayoutStarted(ICancellationSignal cancellationSignal) {
if (DEBUG) {
Log.i(LOG_TAG, "onLayoutStarted()");
}
synchronized (mLock) {
mCancellationSignal = cancellationSignal;
if (isCancelled()) {
cancelSignalQuietlyLocked();
}
}
}
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
if (DEBUG) {
Log.i(LOG_TAG, "onLayoutFinished()");
}
final boolean cancelled;
synchronized (mLock) {
cancelled = isCancelled();
mCancellationSignal = null;
mState = STATE_LAYOUT_COMPLETED;
mTaskQueue.remove(this);
mLock.notifyAll();
}
if (!cancelled) {
mCallback.onLayoutFinished(info, changed);
}
}
@Override
public void onLayoutFailed(CharSequence error) {
if (DEBUG) {
Log.i(LOG_TAG, "onLayoutFailed()");
}
final boolean cancelled;
synchronized (mLock) {
cancelled = isCancelled();
mCancellationSignal = null;
mState = STATE_LAYOUT_COMPLETED;
mTaskQueue.remove(this);
mLock.notifyAll();
}
if (!cancelled) {
mCallback.onLayoutFailed(error);
}
}
};
private ICancellationSignal mCancellationSignal;
public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes,
LayoutResultCallback callback, Bundle metadata) {
mOldAttributes = oldAttributes;
mNewAttributes = newAttributes;
mCallback = callback;
mMetadata = metadata;
}
@Override
public void cancel() {
synchronized (mLock) {
throwIfCancelledLocked();
cancelSignalQuietlyLocked();
}
super.cancel();
}
@Override
protected Void doInBackground(Void... params) {
synchronized (mLock) {
if (DEBUG) {
Log.i(LOG_TAG, "layout()");
}
if (mState != STATE_START_COMPLETED
&& mState != STATE_LAYOUT_COMPLETED
&& mState != STATE_WRITE_COMPLETED) {
throw new IllegalStateException("Invalid state: " + mState);
}
mState = STATE_LAYOUT_STARTED;
}
try {
mRemoteInterface.layout(mOldAttributes, mNewAttributes,
mILayoutResultCallback, mMetadata);
synchronized (mLock) {
while (true) {
if (mState == STATE_LAYOUT_COMPLETED) {
break;
}
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error calling layout", re);
mState = STATE_FAILED;
mTaskQueue.remove(this);
notifyLayoutFailedQuietly();
}
return null;
}
private void cancelSignalQuietlyLocked() {
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error cancelling layout", re);
notifyLayoutFailedQuietly();
}
}
}
public void notifyLayoutFailedQuietly() {
try {
mILayoutResultCallback.onLayoutFailed(null);
} catch (RemoteException re) {
/* ignore */
}
}
private void throwIfCancelledLocked() {
if (isCancelled()) {
throw new IllegalStateException("Already cancelled");
}
}
}
private final class WriteAsyncTask extends QueuedAsyncTask {
private final List<PageRange> mPages;
private final WriteResultCallback mCallback;
private final IWriteResultCallback mIWriteResultCallback =
new IWriteResultCallback.Stub() {
@Override
public void onWriteStarted(ICancellationSignal cancellationSignal) {
if (DEBUG) {
Log.i(LOG_TAG, "onWriteStarted()");
}
synchronized (mLock) {
mCancellationSignal = cancellationSignal;
if (isCancelled()) {
cancelSignalQuietlyLocked();
}
}
}
@Override
public void onWriteFinished(List<PageRange> pages) {
if (DEBUG) {
Log.i(LOG_TAG, "onWriteFinished()");
}
synchronized (mLock) {
mCancellationSignal = null;
mState = STATE_WRITE_COMPLETED;
mTaskQueue.remove(this);
mLock.notifyAll();
}
mCallback.onWriteFinished(pages);
}
@Override
public void onWriteFailed(CharSequence error) {
if (DEBUG) {
Log.i(LOG_TAG, "onWriteFailed()");
}
synchronized (mLock) {
mCancellationSignal = null;
mState = STATE_WRITE_COMPLETED;
mTaskQueue.remove(this);
mLock.notifyAll();
}
Slog.e(LOG_TAG, "Error writing print document: " + error);
mCallback.onWriteFailed(error);
}
};
private ICancellationSignal mCancellationSignal;
private Thread mWriteThread;
public WriteAsyncTask(List<PageRange> pages, WriteResultCallback callback) {
mPages = pages;
mCallback = callback;
}
@Override
public void cancel() {
synchronized (mLock) {
throwIfCancelledLocked();
cancelSignalQuietlyLocked();
mWriteThread.interrupt();
}
super.cancel();
}
@Override
protected Void doInBackground(Void... params) {
if (DEBUG) {
Log.i(LOG_TAG, "write()");
}
synchronized (mLock) {
if (mState != STATE_LAYOUT_COMPLETED
&& mState != STATE_WRITE_COMPLETED) {
throw new IllegalStateException("Invalid state: " + mState);
}
mState = STATE_WRITE_STARTED;
}
InputStream in = null;
OutputStream out = null;
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
synchronized (mLock) {
mWriteThread = Thread.currentThread();
}
try {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
source = pipe[0];
sink = pipe[1];
in = new FileInputStream(source.getFileDescriptor());
out = new FileOutputStream(mFile);
// Async call to initiate the other process writing the data.
mRemoteInterface.write(mPages, sink, mIWriteResultCallback);
// Close the source. It is now held by the client.
sink.close();
sink = null;
final byte[] buffer = new byte[8192];
while (true) {
if (Thread.currentThread().isInterrupted()) {
Thread.currentThread().interrupt();
break;
}
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
}
out.write(buffer, 0, readByteCount);
}
synchronized (mLock) {
while (true) {
if (mState == STATE_WRITE_COMPLETED) {
break;
}
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error writing print document", re);
mState = STATE_FAILED;
mTaskQueue.remove(this);
notifyWriteFailedQuietly();
} catch (IOException ioe) {
Slog.e(LOG_TAG, "Error writing print document", ioe);
mState = STATE_FAILED;
mTaskQueue.remove(this);
notifyWriteFailedQuietly();
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(sink);
IoUtils.closeQuietly(source);
}
return null;
}
private void cancelSignalQuietlyLocked() {
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error cancelling layout", re);
notifyWriteFailedQuietly();
}
}
}
private void notifyWriteFailedQuietly() {
try {
mIWriteResultCallback.onWriteFailed(null);
} catch (RemoteException re) {
/* ignore */
}
}
private void throwIfCancelledLocked() {
if (isCancelled()) {
throw new IllegalStateException("Already cancelled");
}
try {
mRemoteInterface.finish();
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error calling finish()", re);
}
}
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -28,6 +29,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.IBinder.DeathRecipient;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
@@ -46,11 +48,11 @@ import java.util.List;
* and unbinding from the remote implementation. Clients can call methods of
* this class without worrying about when and how to bind/unbind.
*/
final class RemotePrintService {
final class RemotePrintService implements DeathRecipient {
private static final String LOG_TAG = "RemotePrintService";
private static final boolean DEBUG = true;
private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final Context mContext;
@@ -101,6 +103,15 @@ final class RemotePrintService {
mHandler.sendEmptyMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED);
}
@Override
public void binderDied() {
mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
}
private void handleBinderDied() {
ensureBound();
}
private void handleOnAllPrintJobsHandled() {
throwIfDestroyed();
if (isBound()) {
@@ -289,6 +300,7 @@ final class RemotePrintService {
public static final int MSG_START_PRINTER_DISCOVERY = 4;
public static final int MSG_STOP_PRINTER_DISCOVERY = 5;
public static final int MSG_DESTROY = 6;
public static final int MSG_BINDER_DIED = 7;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -324,6 +336,10 @@ final class RemotePrintService {
case MSG_DESTROY: {
handleDestroy();
} break;
case MSG_BINDER_DIED: {
handleBinderDied();
} break;
}
}
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -54,7 +55,7 @@ final class RemotePrintSpooler {
private static final String LOG_TAG = "RemotePrintSpooler";
private static final boolean DEBUG = true;
private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
@@ -88,6 +89,8 @@ final class RemotePrintSpooler {
private boolean mDestroyed;
private boolean mCanUnbind;
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
@@ -111,6 +114,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
@@ -122,6 +126,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print jobs.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print jobs.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return null;
}
@@ -131,6 +140,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
@@ -142,6 +152,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error creating print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error creating print job.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return null;
}
@@ -150,6 +165,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()");
@@ -161,6 +177,11 @@ final class RemotePrintSpooler {
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;
}
@@ -169,6 +190,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
@@ -183,6 +205,10 @@ final class RemotePrintSpooler {
// We passed the file descriptor across and now the other
// side is responsible to close it, so close the local copy.
IoUtils.closeQuietly(fd);
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
@@ -190,6 +216,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
@@ -201,6 +228,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print job info.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print job info.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return null;
}
@@ -209,6 +241,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
@@ -220,6 +253,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job state.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job state.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return false;
}
@@ -228,6 +266,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
@@ -239,6 +278,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job tag.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job tag.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return false;
}
@@ -247,6 +291,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
@@ -258,6 +303,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error asking for active print job notification.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error asking for active print job notification.", te);
} finally {
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
@@ -270,6 +320,7 @@ final class RemotePrintSpooler {
throwIfDestroyedLocked();
unbindLocked();
mDestroyed = true;
mCanUnbind = false;
}
}
@@ -314,15 +365,29 @@ final class RemotePrintSpooler {
/* ignore */
}
}
mCanUnbind = true;
mLock.notifyAll();
}
private void unbindLocked() {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
while (true) {
if (mCanUnbind) {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
}
clearClientLocked();
mRemoteInstance = null;
mContext.unbindService(mServiceConnection);
return;
}
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
clearClientLocked();
mRemoteInstance = null;
mContext.unbindService(mServiceConnection);
}
private void setClientLocked() {