diff options
25 files changed, 1753 insertions, 1202 deletions
diff --git a/api/current.txt b/api/current.txt index 8c0211a..52bdf68 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18462,7 +18462,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(); @@ -18488,7 +18487,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); @@ -18574,18 +18572,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 { @@ -18616,6 +18616,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(); diff --git a/core/java/android/print/FileDocumentAdapter.java b/core/java/android/print/FileDocumentAdapter.java index c7011f4..d642a61 100644 --- a/core/java/android/print/FileDocumentAdapter.java +++ b/core/java/android/print/FileDocumentAdapter.java @@ -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 diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl index e4d79f3..43b8c30 100644 --- a/core/java/android/print/ILayoutResultCallback.aidl +++ b/core/java/android/print/ILayoutResultCallback.aidl @@ -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); } diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index 04da157..b12c922 100644 --- a/core/java/android/print/IPrintDocumentAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -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(); } diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index a466e74..37ae2ca 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -33,5 +33,4 @@ interface IPrintManager { in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes, int appId, int userId); void cancelPrintJob(int printJobId, int appId, int userId); - } diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl index 7912964..51b5439 100644 --- a/core/java/android/print/IPrintSpoolerCallbacks.aidl +++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl @@ -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); } diff --git a/core/java/android/print/IPrintSpoolerObserver.aidl b/core/java/android/print/IPrintSpoolerObserver.aidl deleted file mode 100644 index 7b8f40e..0000000 --- a/core/java/android/print/IPrintSpoolerObserver.aidl +++ /dev/null @@ -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(); -} diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl index d5428b1..8281c4e 100644 --- a/core/java/android/print/IWriteResultCallback.aidl +++ b/core/java/android/print/IWriteResultCallback.aidl @@ -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); } diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java index 9257a04..ba455f6 100644 --- a/core/java/android/print/PageRange.java +++ b/core/java/android/print/PageRange.java @@ -93,8 +93,38 @@ public final class PageRange implements Parcelable { } @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(); diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index 87d75c0..911e380 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -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; } /** @@ -954,6 +1021,44 @@ public final class PrintAttributes implements Parcelable { } @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(); builder.append("MediaSize{"); @@ -1061,6 +1166,44 @@ public final class PrintAttributes implements Parcelable { } @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(); builder.append("Resolution{"); @@ -1166,6 +1309,44 @@ public final class PrintAttributes implements Parcelable { } @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(); builder.append("Margins{"); @@ -1235,6 +1416,36 @@ public final class PrintAttributes implements Parcelable { } @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(); builder.append("Tray{"); @@ -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); @@ -1471,17 +1667,6 @@ public final class PrintAttributes implements Parcelable { } /** - * 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. * * @return The new instance. diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java index 1f83a45..d320226 100644 --- a/core/java/android/print/PrintDocumentAdapter.java +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -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 */ + } } } diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index 7d42b3a..29e8e7c 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -111,6 +111,36 @@ public final class PrintDocumentInfo implements Parcelable { } @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(); builder.append("PrintDocumentInfo{"); diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java index a5e0b79..de28bd3 100644 --- a/core/java/android/print/PrintJob.java +++ b/core/java/android/print/PrintJob.java @@ -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 diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java index 97384d9..39546f3 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -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); @@ -310,6 +322,29 @@ public final class PrintJobInfo implements Parcelable { } /** + * 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. * * @return The included pages or <code>null</code> if not set. @@ -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(); } diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index f9f53f6..9e8cfad 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -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); - } - } - } } diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java index da3b6bc..c0daa6e 100644 --- a/core/java/android/print/PrinterInfo.java +++ b/core/java/android/print/PrinterInfo.java @@ -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); diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java index 80530a7..0ac5a13 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -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)) { diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity.xml b/packages/PrintSpooler/res/layout/print_job_config_activity.xml index 32bc15a..a4105ea 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity.xml @@ -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> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 27540d7..1762693 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -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–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] --> diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 1e1cc24..86c4f37 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -24,9 +24,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -34,24 +32,27 @@ import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.UserHandle; +import android.print.ILayoutResultCallback; import android.print.IPrintDocumentAdapter; import android.print.IPrinterDiscoveryObserver; +import android.print.IWriteResultCallback; import android.print.PageRange; import android.print.PrintAttributes; import android.print.PrintAttributes.MediaSize; -import android.print.PrintDocumentAdapter.LayoutResultCallback; -import android.print.PrintDocumentAdapter.WriteResultCallback; +import android.print.PrintDocumentAdapter; import android.print.PrintDocumentInfo; import android.print.PrintJobInfo; import android.print.PrinterId; import android.print.PrinterInfo; import android.text.Editable; import android.text.TextUtils; +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; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -64,10 +65,13 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; -import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -76,15 +80,31 @@ import java.util.regex.Pattern; */ public class PrintJobConfigActivity extends Activity { - private static final boolean DEBUG = false; + private static final String LOG_TAG = "PrintJobConfigActivity"; - private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName(); + private static final boolean DEBUG = true && Build.IS_DEBUGGABLE; - public static final String EXTRA_PRINTABLE = "printable"; - public static final String EXTRA_APP_ID = "appId"; - public static final String EXTRA_ATTRIBUTES = "attributes"; + private static final boolean LIVE_PREVIEW_SUPPORTED = false; + + public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter"; + public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes"; public static final String EXTRA_PRINT_JOB_ID = "printJobId"; + private static final int CONTROLLER_STATE_INITIALIZED = 1; + private static final int CONTROLLER_STATE_STARTED = 2; + private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3; + private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4; + private static final int CONTROLLER_STATE_WRITE_STARTED = 5; + private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6; + private static final int CONTROLLER_STATE_FINISHED = 7; + private static final int CONTROLLER_STATE_FAILED = 8; + private static final int CONTROLLER_STATE_CANCELLED = 9; + + private static final int EDITOR_STATE_INITIALIZED = 1; + private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; + private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3; + private static final int EDITOR_STATE_CANCELLED = 4; + private static final int MIN_COPIES = 1; private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d"); @@ -95,31 +115,12 @@ public class PrintJobConfigActivity extends Activity { private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile( "([0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*[,]?[\\s]*)+"); - private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this); - - private Handler mHandler; - - private Editor mEditor; - - private IPrinterDiscoveryObserver mPrinterDiscoveryObserver; - - private int mAppId; - private int mPrintJobId; + public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES}; private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().create(); private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().create(); private final PrintAttributes mTempPrintAttributes = new PrintAttributes.Builder().create(); - private RemotePrintDocumentAdapter mRemotePrintAdapter; - - private boolean mPrintConfirmed; - - private boolean mStarted; - - private IBinder mIPrintDocumentAdapter; - - private PrintDocumentInfo mPrintDocumentInfo; - private final DeathRecipient mDeathRecipient = new DeathRecipient() { @Override public void binderDied() { @@ -127,369 +128,484 @@ public class PrintJobConfigActivity extends Activity { } }; - @Override - protected void onDestroy() { - mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); - super.onDestroy(); - } + private PrintSpooler mSpooler; + private Editor mEditor; + private Document mDocument; + private PrintController mController; + private PrinterDiscoveryObserver mPrinterDiscoveryObserver; + + private int mPrintJobId; + + private IBinder mIPrintDocumentAdapter; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.print_job_config_activity); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN - | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + + Bundle extras = getIntent().getExtras(); + + mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1); + if (mPrintJobId < 0) { + throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId); + } + + mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER); + if (mIPrintDocumentAdapter == null) { + throw new IllegalArgumentException("PrintDocumentAdapter cannot be null"); + } + + PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_PRINT_ATTRIBUTES); + if (attributes != null) { + mCurrPrintAttributes.copyFrom(attributes); + } - mHandler = new MyHandler(Looper.getMainLooper()); + mSpooler = PrintSpooler.getInstance(this); mEditor = new Editor(); + mDocument = new Document(); + mController = new PrintController(new RemotePrintDocumentAdapter( + IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), + mSpooler.generateFileForPrintJob(mPrintJobId))); } @Override protected void onResume() { super.onResume(); - mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver); - notifyPrintableStartIfNeeded(); + try { + mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); + } catch (RemoteException re) { + finish(); + return; + } + mController.initialize(); + mEditor.initialize(); + mPrinterDiscoveryObserver = new PrinterDiscoveryObserver(mEditor, getMainLooper()); + mSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver); } @Override protected void onPause() { + mSpooler.stopPrinterDiscovery(); + mPrinterDiscoveryObserver.destroy(); + mPrinterDiscoveryObserver = null; + if (mController.isCancelled() || mController.isFailed()) { + mSpooler.setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_CANCELED); + } else if (mController.hasStarted()) { + mController.finish(); + if (mEditor.isPrintConfirmed()) { + if (mController.isFinished()) { + mSpooler.setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_QUEUED); + } else { + mSpooler.setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_CANCELED); + } + } + } + mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); super.onPause(); - mPrintSpooler.stopPrinterDiscovery(); - notifyPrintableFinishIfNeeded(); } - private void notifyPrintableStartIfNeeded() { - if (mEditor.getCurrentPrinter() == null - || mStarted) { - return; + public boolean onTouchEvent(MotionEvent event) { + if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed() + && getWindow().shouldCloseOnTouch(this, event)) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } + mEditor.cancel(); + return true; } - mStarted = true; - mRemotePrintAdapter.start(); + return super.onTouchEvent(event); } - private void updatePrintableContentIfNeeded() { - if (!mStarted) { - return; + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + event.startTracking(); } + return super.onKeyDown(keyCode, event); + } - mPrintSpooler.setPrintJobAttributes(mPrintJobId, mCurrPrintAttributes); - - mRemotePrintAdapter.cancel(); - mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FINISHED); - mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FAILED); - - // TODO: Implement setting the print preview attribute - mRemotePrintAdapter.layout(mOldPrintAttributes, - mCurrPrintAttributes, new LayoutResultCallback() { - @Override - public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { - mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0, - 0, info).sendToTarget(); + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + && !event.isCanceled()) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); } + mEditor.cancel(); + return true; + } + return super.onKeyUp(keyCode, event); + } - @Override - public void onLayoutFailed(CharSequence error) { - mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget(); - } - }, new Bundle()); + private boolean printAttributesChanged() { + return !mOldPrintAttributes.equals(mCurrPrintAttributes); } - private void handleOnLayoutFinished(PrintDocumentInfo info, boolean changed) { - mPrintDocumentInfo = info; + private class PrintController { + private final AtomicInteger mRequestCounter = new AtomicInteger(); - mEditor.updateUiIfNeeded(); + private final RemotePrintDocumentAdapter mRemotePrintAdapter; - // TODO: Handle the case of unchanged content - mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info); + private final Handler mHandler; - // TODO: Implement page selector. - final List<PageRange> pages = new ArrayList<PageRange>(); - pages.add(PageRange.ALL_PAGES); + private int mControllerState = CONTROLLER_STATE_INITIALIZED; - mRemotePrintAdapter.write(pages, new WriteResultCallback() { + private PageRange[] mRequestedPages; + + private Bundle mMetadata = new Bundle(); + + private final ILayoutResultCallback mILayoutResultCallback = + new ILayoutResultCallback.Stub() { @Override - public void onWriteFinished(List<PageRange> pages) { - mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget(); + public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0, + 0, info).sendToTarget(); + } } @Override - public void onWriteFailed(CharSequence error) { - mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget(); + public void onLayoutFailed(CharSequence error, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget(); + } } - }); - } - - private void handleOnLayoutFailed(CharSequence error) { - Log.e(LOG_TAG, "Error during layout: " + error); - finishActivity(Activity.RESULT_CANCELED); - } + }; - private void handleOnWriteFinished(List<PageRange> pages) { - // TODO: Now we have to allow the preview button - mEditor.updatePrintPreview(mRemotePrintAdapter.getFile()); - } + private IWriteResultCallback mIWriteResultCallback = new IWriteResultCallback.Stub() { + @Override + public void onWriteFinished(PageRange[] pages, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget(); + } + } - private void handleOnWriteFailed(CharSequence error) { - Log.e(LOG_TAG, "Error write layout: " + error); - finishActivity(Activity.RESULT_CANCELED); - } + @Override + public void onWriteFailed(CharSequence error, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget(); + } + } + }; - private void notifyPrintableFinishIfNeeded() { - if (!mStarted) { - return; + public PrintController(RemotePrintDocumentAdapter adapter) { + mRemotePrintAdapter = adapter; + mHandler = new MyHandler(Looper.getMainLooper()); } - if (!mPrintConfirmed) { - mRemotePrintAdapter.cancel(); + public void initialize() { + mControllerState = CONTROLLER_STATE_INITIALIZED; } - mRemotePrintAdapter.finish(); - PrinterInfo printer = mEditor.getCurrentPrinter(); - // If canceled or no printer, nothing to do. - if (!mPrintConfirmed || printer == null) { - // Update the print job's status. - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_CANCELED); - return; + public void cancel() { + mControllerState = CONTROLLER_STATE_CANCELLED; } - // Update the print job's printer. - mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printer.getId()); + public boolean isCancelled() { + return (mControllerState == CONTROLLER_STATE_CANCELLED); + } - // Update the print job's status. - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_QUEUED); + public boolean isFinished() { + return (mControllerState == CONTROLLER_STATE_FINISHED); + } - if (DEBUG) { - if (mPrintConfirmed) { - File file = mRemotePrintAdapter.getFile(); - if (file.exists()) { - new ViewSpooledFileAsyncTask(file).executeOnExecutor( - AsyncTask.SERIAL_EXECUTOR, (Void[]) null); - } - } + public boolean isFailed() { + return (mControllerState == CONTROLLER_STATE_FAILED); } - } - private boolean hasPdfViewer() { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType("application/pdf"); - return !getPackageManager().queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); - } + public boolean hasStarted() { + return mControllerState >= CONTROLLER_STATE_STARTED; + } - // Caution: Use this only for debugging - private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> { + public boolean hasPerformedLayout() { + return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED; + } - private final File mFile; + public boolean isWorking() { + return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED + || mControllerState == CONTROLLER_STATE_WRITE_STARTED; + } - public ViewSpooledFileAsyncTask(File file) { - mFile = file; + public void start() { + mControllerState = CONTROLLER_STATE_STARTED; + mRemotePrintAdapter.start(); } - @Override - protected Void doInBackground(Void... params) { - mFile.setExecutable(true, false); - mFile.setWritable(true, false); - mFile.setReadable(true, false); + public void update() { + if (!printAttributesChanged()) { + // If the attributes changes, then we do not do a layout but may + // have to ask the app to write some pages. Hence, pretend layout + // completed and nothing changed, so we handle writing as usual. + handleOnLayoutFinished(mDocument.info, false); + } else { + mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes); - final long identity = Binder.clearCallingIdentity(); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.fromFile(mFile), "application/pdf"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityAsUser(intent, null, UserHandle.CURRENT); - Binder.restoreCallingIdentity(identity); - return null; - } - } + mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW, + !mEditor.isPrintConfirmed()); - private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { - private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1; - private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2; + mControllerState = CONTROLLER_STATE_LAYOUT_STARTED; - private final Handler mHandler; + mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes, + mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet()); - @SuppressWarnings("unchecked") - public PrintDiscoveryObserver(Looper looper) { - mHandler = new Handler(looper, null, true) { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ADD_DICOVERED_PRINTERS: { - List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; - mEditor.addPrinters(printers); - } break; - case MESSAGE_REMOVE_DICOVERED_PRINTERS: { - List<PrinterId> printerIds = (List<PrinterId>) message.obj; - mEditor.removePrinters(printerIds); - } break; - } - } - }; + mOldPrintAttributes.copyFrom(mCurrPrintAttributes); + } } - @Override - public void addDiscoveredPrinters(List<PrinterInfo> printers) { - mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget(); + public void finish() { + mControllerState = CONTROLLER_STATE_FINISHED; + mRemotePrintAdapter.finish(); } - @Override - public void removeDiscoveredPrinters(List<PrinterId> printers) { - mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget(); - } - } + private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) { + if (isCancelled()) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } - private final class SpinnerItem<T> { - final T value; - CharSequence label; + mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED; - public SpinnerItem(T value, CharSequence label) { - this.value = value; - this.label = label; - } + // If the info changed, we update the document and the print job, + // and update the UI since the the page range selection may have + // become invalid. + final boolean infoChanged = !info.equals(mDocument.info); + if (infoChanged) { + mDocument.info = info; + mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info); + mEditor.updateUi(); + } - public String toString() { - return label.toString(); - } - } + // If the document info or the layout changed, then + // drop the pages since we have to fetch them again. + if (infoChanged || layoutChanged) { + mDocument.pages = null; + } - /** - * An instance of this class class is intended to be the first focusable - * in a layout to which the system automatically gives focus. It performs - * some voodoo to avoid the first tap on it to start an edit mode, rather - * to bring up the IME, i.e. to get the behavior as if the view was not - * focused. - */ - public static final class CustomEditText extends EditText { - private boolean mClickedBeforeFocus; + // No pages means that the user selected an invalid range while we + // were doing a layout or the layout returned a document info for + // which the selected range is invalid. In such a case we do not + // write anything and wait for the user to fix the range which will + // trigger an update. + mRequestedPages = mEditor.getRequestedPages(); + if (mRequestedPages == null) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } - public CustomEditText(Context context, AttributeSet attrs) { - super(context, attrs); - } + // If the info and the layout did not change and we already have + // the requested pages, then nothing else to do. + if (!infoChanged && !layoutChanged + && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } - @Override - public boolean performClick() { - super.performClick(); - if (isFocused() && !mClickedBeforeFocus) { - clearFocus(); - requestFocus(); + // If we do not support live preview and the current layout is + // not for preview purposes, i.e. the user did not poke the + // preview button, then just skip the write. + if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed() + && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; } - mClickedBeforeFocus = true; - return true; + + // Request a write of the pages of interest. + mControllerState = CONTROLLER_STATE_WRITE_STARTED; + mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback, + mRequestCounter.incrementAndGet()); } - @Override - public void setError(CharSequence error, Drawable icon) { - setCompoundDrawables(null, null, icon, null); + private void handleOnLayoutFailed(CharSequence error) { + mControllerState = CONTROLLER_STATE_FAILED; + // TODO: We need some UI for announcing an error. + Log.e(LOG_TAG, "Error during layout: " + error); + PrintJobConfigActivity.this.finish(); } - protected void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - if (!gainFocus) { - mClickedBeforeFocus = false; + private void handleOnWriteFinished(PageRange[] pages) { + if (isCancelled()) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; } - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - } - } - private final class MyHandler extends Handler { - public static final int MSG_ON_LAYOUT_FINISHED = 1; - public static final int MSG_ON_LAYOUT_FAILED = 2; - public static final int MSG_ON_WRITE_FINISHED = 3; - public static final int MSG_ON_WRITE_FAILED = 4; + mControllerState = CONTROLLER_STATE_WRITE_COMPLETED; + + // Update which pages we have fetched. + mDocument.pages = PageRangeUtils.normalize(pages); - public MyHandler(Looper looper) { - super(looper, null, false); + if (DEBUG) { + Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages) + + " and got: " + Arrays.toString(mDocument.pages)); + } + + // Adjust the print job pages based on what was requested and written. + // The cases are ordered in the most expected to the least expected. + if (Arrays.equals(mDocument.pages, mRequestedPages)) { + // We got a document with exactly the pages we wanted. Hence, + // the printer has to print all pages in the data. + mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY); + } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) { + // We requested specific pages but got all of them. Hence, + // the printer has to print only the requested pages. + mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages); + } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { + // We requested specific pages and got more but not all pages. + // Hence, we have to offset appropriately the printed pages to + // exclude the pages we did not request. Note that pages is + // guaranteed to be not null and not empty. + final int offset = mDocument.pages[0].getStart() - pages[0].getStart(); + PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length); + PageRangeUtils.offsetStart(offsetPages, offset); + mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages); + } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY) + && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0 + && mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) { + // We requested all pages via the special constant and got all + // of them as an explicit enumeration. Hence, the printer has + // to print only the requested pages. + mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages); + } else { + // We did not get the pages we requested, then the application + // misbehaves, so we fail quickly. + // TODO: We need some UI for announcing an error. + mControllerState = CONTROLLER_STATE_FAILED; + Log.e(LOG_TAG, "Received invalid pages from the app"); + PrintJobConfigActivity.this.finish(); + } + + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } } - @Override - @SuppressWarnings("unchecked") - public void handleMessage(Message message) { - switch (message.what) { - case MSG_ON_LAYOUT_FINISHED: { - PrintDocumentInfo info = (PrintDocumentInfo) message.obj; - final boolean changed = (message.arg1 == 1); - handleOnLayoutFinished(info, changed); - } break; - - case MSG_ON_LAYOUT_FAILED: { - CharSequence error = (CharSequence) message.obj; - handleOnLayoutFailed(error); - } break; - - case MSG_ON_WRITE_FINISHED: { - List<PageRange> pages = (List<PageRange>) message.obj; - handleOnWriteFinished(pages); - } break; - - case MSG_ON_WRITE_FAILED: { - CharSequence error = (CharSequence) message.obj; - handleOnWriteFailed(error); - } break; + private void handleOnWriteFailed(CharSequence error) { + mControllerState = CONTROLLER_STATE_FAILED; + Log.e(LOG_TAG, "Error during write: " + error); + PrintJobConfigActivity.this.finish(); + } + + private final class MyHandler extends Handler { + public static final int MSG_ON_LAYOUT_FINISHED = 1; + public static final int MSG_ON_LAYOUT_FAILED = 2; + public static final int MSG_ON_WRITE_FINISHED = 3; + public static final int MSG_ON_WRITE_FAILED = 4; + + public MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_ON_LAYOUT_FINISHED: { + PrintDocumentInfo info = (PrintDocumentInfo) message.obj; + final boolean changed = (message.arg1 == 1); + mController.handleOnLayoutFinished(info, changed); + } break; + + case MSG_ON_LAYOUT_FAILED: { + CharSequence error = (CharSequence) message.obj; + mController.handleOnLayoutFailed(error); + } break; + + case MSG_ON_WRITE_FINISHED: { + PageRange[] pages = (PageRange[]) message.obj; + mController.handleOnWriteFinished(pages); + } break; + + case MSG_ON_WRITE_FAILED: { + CharSequence error = (CharSequence) message.obj; + mController.handleOnWriteFailed(error); + } break; + } } } } - private class Editor { - private EditText mCopiesEditText; + private final class Editor { + private final EditText mCopiesEditText; + + private final TextView mRangeTitle; + private final EditText mRangeEditText; - private EditText mRangeEditText; + private final Spinner mDestinationSpinner; + private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter; - private Spinner mDestinationSpinner; - public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter; + private final Spinner mMediaSizeSpinner; + private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; - private Spinner mMediaSizeSpinner; - public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter; + private final Spinner mColorModeSpinner; + private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; - private Spinner mColorModeSpinner; - public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter; + private final Spinner mOrientationSpinner; + private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; - private Spinner mOrientationSpinner; - public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; + private final Spinner mRangeOptionsSpinner; + private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter; - private Spinner mRangeOptionsSpinner; - public ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter; + private final SimpleStringSplitter mStringCommaSplitter = + new SimpleStringSplitter(','); - private Button mPrintPreviewButton; + private final Button mPrintPreviewButton; - private Button mPrintButton; + private final Button mPrintButton; private final OnItemSelectedListener mOnItemSelectedListener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) { if (spinner == mDestinationSpinner) { - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.clear(); - final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); - if (selectedIndex >= 0) { - mDestinationSpinnerAdapter.getItem(selectedIndex).value.getDefaults( - mCurrPrintAttributes); + SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position); + if (dstItem != null) { + mSpooler.setPrintJobPrinterIdNoPersistence(mPrintJobId, dstItem.value.getId()); + dstItem.value.getDefaults(mCurrPrintAttributes); + } + updateUi(); + if (!mController.hasStarted()) { + mController.start(); + } + if (!hasErrors()) { + mController.update(); } - updateUiIfNeeded(); - notifyPrintableStartIfNeeded(); - updatePrintableContentIfNeeded(); } else if (spinner == mMediaSizeSpinner) { SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.setMediaSize(mediaItem.value); - updatePrintableContentIfNeeded(); + if (!hasErrors()) { + mController.update(); + } } else if (spinner == mColorModeSpinner) { SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position); - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.setColorMode(colorModeItem.value); - updatePrintableContentIfNeeded(); + if (!hasErrors()) { + mController.update(); + } } else if (spinner == mOrientationSpinner) { SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position); - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.setOrientation(orientationItem.value); - updatePrintableContentIfNeeded(); + if (!hasErrors()) { + mController.update(); + } } else if (spinner == mRangeOptionsSpinner) { - updateUiIfNeeded(); - updatePrintableContentIfNeeded(); + updateUi(); + if (!hasErrors()) { + mController.update(); + } } } @@ -512,19 +628,28 @@ public class PrintJobConfigActivity extends Activity { @Override public void afterTextChanged(Editable editable) { + final boolean hadErrors = hasErrors(); + if (editable.length() == 0) { mCopiesEditText.setError(""); - mPrintButton.setEnabled(false); + updateUi(); return; } + final int copies = Integer.parseInt(editable.toString()); if (copies < MIN_COPIES) { mCopiesEditText.setError(""); - mPrintButton.setEnabled(false); + updateUi(); return; } - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); - mCurrPrintAttributes.setCopies(copies); + + mCopiesEditText.setError(null); + mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies); + updateUi(); + + if (hadErrors && !hasErrors() && printAttributesChanged()) { + mController.update(); + } } }; @@ -541,18 +666,20 @@ public class PrintJobConfigActivity extends Activity { @Override public void afterTextChanged(Editable editable) { + final boolean hadErrors = hasErrors(); + String text = editable.toString(); if (TextUtils.isEmpty(text)) { mRangeEditText.setError(""); - mPrintButton.setEnabled(false); + updateUi(); return; } String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////"); if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) { mRangeEditText.setError(""); - mPrintButton.setEnabled(false); + updateUi(); return; } @@ -560,100 +687,36 @@ public class PrintJobConfigActivity extends Activity { while (matcher.find()) { String numericString = text.substring(matcher.start(), matcher.end()); final int pageIndex = Integer.parseInt(numericString); - if (pageIndex < 1 || pageIndex > mPrintDocumentInfo.getPageCount()) { + if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) { mRangeEditText.setError(""); - mPrintButton.setEnabled(false); + updateUi(); return; } } + //TODO: Catch the error if start is less grater than the end. + mRangeEditText.setError(null); mPrintButton.setEnabled(true); - } - }; - - public Editor() { - Bundle extras = getIntent().getExtras(); - - mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1); - if (mPrintJobId < 0) { - throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId); - } - - mAppId = extras.getInt(EXTRA_APP_ID, -1); - if (mAppId < 0) { - throw new IllegalArgumentException("Invalid app id: " + mAppId); - } - - PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES); - if (attributes == null) { - mCurrPrintAttributes.copyFrom(attributes); - } + updateUi(); - mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE); - if (mIPrintDocumentAdapter == null) { - throw new IllegalArgumentException("Printable cannot be null"); - } - mRemotePrintAdapter = new RemotePrintDocumentAdapter( - IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), - mPrintSpooler.generateFileForPrintJob(mPrintJobId)); - - try { - mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); - } catch (RemoteException re) { - finish(); + if (hadErrors && !hasErrors() && printAttributesChanged()) { + updateUi(); + } } + }; - mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper()); - - bindUi(); - } + private int mEditorState; - private void bindUi() { + public Editor() { // Copies mCopiesEditText = (EditText) findViewById(R.id.copies_edittext); - mCopiesEditText.setText(String.valueOf(MIN_COPIES)); mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); - mCopiesEditText.setText(String.valueOf( - Math.max(mCurrPrintAttributes.getCopies(), MIN_COPIES))); mCopiesEditText.selectAll(); // Destination. mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner); - mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>( - PrintJobConfigActivity.this, R.layout.spinner_dropdown_item) { - @Override - public View getDropDownView(int position, View convertView, - ViewGroup parent) { - return getView(position, convertView, parent); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = getLayoutInflater().inflate( - R.layout.spinner_dropdown_item, parent, false); - } - - PrinterInfo printerInfo = getItem(position).value; - TextView title = (TextView) convertView.findViewById(R.id.title); - title.setText(printerInfo.getLabel()); - - try { - TextView subtitle = (TextView) - convertView.findViewById(R.id.subtitle); - PackageManager pm = getPackageManager(); - PackageInfo packageInfo = pm.getPackageInfo( - printerInfo.getId().getService().getPackageName(), 0); - subtitle.setText(packageInfo.applicationInfo.loadLabel(pm)); - subtitle.setVisibility(View.VISIBLE); - } catch (NameNotFoundException nnfe) { - /* ignore */ - } - - return convertView; - } - }; + mDestinationSpinnerAdapter = new DestinationAdapter(); mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter); mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); @@ -682,6 +745,7 @@ public class PrintJobConfigActivity extends Activity { mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); // Range + mRangeTitle = (TextView) findViewById(R.id.page_range_title); mRangeEditText = (EditText) findViewById(R.id.page_range_edittext); mRangeEditText.addTextChangedListener(mRangeTextWatcher); @@ -690,8 +754,6 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item, R.id.title); - mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter); - mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); final int[] rangeOptionsValues = getResources().getIntArray( R.array.page_options_values); String[] rangeOptionsLabels = getResources().getStringArray( @@ -701,13 +763,29 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>( 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); mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button); mPrintPreviewButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - // TODO: Implement + mEditor.confirmPreview(); + // TODO: Implement me + Toast.makeText(PrintJobConfigActivity.this, + "Stop poking me! Not implemented yet :)", + Toast.LENGTH_LONG).show(); } }); @@ -715,21 +793,106 @@ public class PrintJobConfigActivity extends Activity { mPrintButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - mPrintConfirmed = true; - finish(); + mEditor.confirmPrint(); + updateUi(); + mController.update(); } }); } - private void updateUiIfNeeded() { + public void initialize() { + mEditorState = EDITOR_STATE_INITIALIZED; + mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION); + } + + public boolean isCancelled() { + return mEditorState == EDITOR_STATE_CANCELLED; + } + + public void cancel() { + mEditorState = EDITOR_STATE_CANCELLED; + mController.cancel(); + updateUi(); + } + + public boolean isDone() { + return isPrintConfirmed() || isPreviewConfirmed() || isCancelled(); + } + + public boolean isPrintConfirmed() { + return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; + } + + public void confirmPrint() { + mEditorState = EDITOR_STATE_CONFIRMED_PRINT; + } + + public boolean isPreviewConfirmed() { + return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; + } + + public void confirmPreview() { + mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW; + } + + public PageRange[] getRequestedPages() { + if (hasErrors()) { + return null; + } + if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) { + List<PageRange> pageRanges = new ArrayList<PageRange>(); + mStringCommaSplitter.setString(mRangeEditText.getText().toString()); + + while (mStringCommaSplitter.hasNext()) { + String range = mStringCommaSplitter.next().trim(); + final int dashIndex = range.indexOf('-'); + final int fromIndex; + final int toIndex; + + if (dashIndex > 0) { + fromIndex = Integer.parseInt(range.substring(0, dashIndex)) - 1; + toIndex = Integer.parseInt(range.substring( + dashIndex + 1, range.length())) - 1; + } else { + fromIndex = toIndex = Integer.parseInt(range); + } + + PageRange pageRange = new PageRange(fromIndex, toIndex); + pageRanges.add(pageRange); + } + + PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; + pageRanges.toArray(pageRangesArray); + + return PageRangeUtils.normalize(pageRangesArray); + } + + return ALL_PAGES_ARRAY; + } + + public void updateUi() { + if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) { + mDestinationSpinner.setEnabled(false); + mCopiesEditText.setEnabled(false); + mMediaSizeSpinner.setEnabled(false); + mColorModeSpinner.setEnabled(false); + mOrientationSpinner.setEnabled(false); + mRangeOptionsSpinner.setEnabled(false); + mRangeEditText.setEnabled(false); + mPrintPreviewButton.setEnabled(false); + mPrintButton.setEnabled(false); + return; + } + final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); if (selectedIndex < 0) { // Destination mDestinationSpinner.setEnabled(false); - // Copies - mCopiesEditText.setText("1"); + mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher); + mCopiesEditText.setText(String.valueOf(MIN_COPIES)); + mCopiesEditText.addTextChangedListener(mCopiesTextWatcher); mCopiesEditText.setEnabled(false); // Media size @@ -751,7 +914,11 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinner.setOnItemSelectedListener(null); 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); mRangeEditText.setEnabled(false); mRangeEditText.setVisibility(View.INVISIBLE); @@ -884,46 +1051,65 @@ public class PrintJobConfigActivity extends Activity { } // Range options - if (mPrintDocumentInfo != null && (mPrintDocumentInfo.getPageCount() > 1 - || mPrintDocumentInfo.getPageCount() - == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) { + PrintDocumentInfo info = mDocument.info; + if (info != null && (info.getPageCount() > 1 + || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) { mRangeOptionsSpinner.setEnabled(true); if (mRangeOptionsSpinner.getSelectedItemPosition() > 0 && !mRangeEditText.isEnabled()) { mRangeEditText.setEnabled(true); - mRangeEditText.setError(""); mRangeEditText.setVisibility(View.VISIBLE); mRangeEditText.requestFocus(); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); imm.showSoftInput(mRangeEditText, 0); } + final int pageCount = mDocument.info.getPageCount(); + mRangeTitle.setText(getString(R.string.label_pages, + (pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) + ? getString(R.string.page_count_unknown) + : String.valueOf(pageCount))); } else { mRangeOptionsSpinner.setOnItemSelectedListener(null); mRangeOptionsSpinner.setSelection(0); mRangeOptionsSpinner.setEnabled(false); + mRangeTitle.setText(getString(R.string.label_pages, + getString(R.string.page_count_unknown))); mRangeEditText.setEnabled(false); - mRangeEditText.setText(""); mRangeEditText.setVisibility(View.INVISIBLE); } - // Print preview - mPrintPreviewButton.setEnabled(true); - if (hasPdfViewer()) { - mPrintPreviewButton.setText(getString(R.string.print_preview)); + // Print/Print preview + if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1 + && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors())) + || (mRangeOptionsSpinner.getSelectedItemPosition() == 0 + && (!mController.hasPerformedLayout() || hasErrors()))) { + mPrintPreviewButton.setEnabled(false); + mPrintButton.setEnabled(false); } else { - mPrintPreviewButton.setText(getString(R.string.install_for_print_preview)); + mPrintPreviewButton.setEnabled(true); + if (hasPdfViewer()) { + mPrintPreviewButton.setText(getString(R.string.print_preview)); + } else { + mPrintPreviewButton.setText(getString(R.string.install_for_print_preview)); + } + mPrintButton.setEnabled(true); } - // Print - mPrintButton.setEnabled(true); + // Copies + if (mCopiesEditText.getError() == null + && TextUtils.isEmpty(mCopiesEditText.getText())) { + 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().postCallback(Choreographer.CALLBACK_TRAVERSAL, + Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL, new Runnable() { @Override public void run() { @@ -932,15 +1118,7 @@ public class PrintJobConfigActivity extends Activity { mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener); mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener); } - }, null); - } - - public PrinterInfo getCurrentPrinter() { - final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); - if (selectedIndex >= 0) { - return mDestinationSpinnerAdapter.getItem(selectedIndex).value; - } - return null; + }, null, Choreographer.getFrameDelay() * 2); } public void addPrinters(List<PrinterInfo> addedPrinters) { @@ -995,8 +1173,261 @@ public class PrintJobConfigActivity extends Activity { } } - private void updatePrintPreview(File file) { - // TODO: Implement + private boolean hasErrors() { + return mRangeEditText.getError() != null + || mCopiesEditText.getError() != null; + } + + private boolean hasPdfViewer() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType("application/pdf"); + return !getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); + } + + private final class SpinnerItem<T> { + final T value; + CharSequence label; + + public SpinnerItem(T value, CharSequence label) { + this.value = value; + this.label = label; + } + + public String toString() { + return label.toString(); + } + } + + private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> { + + public DestinationAdapter() { + super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item); + } + + @Override + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + return getView(position, convertView, parent); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getLayoutInflater().inflate( + R.layout.spinner_dropdown_item, parent, false); + } + + PrinterInfo printerInfo = getItem(position).value; + TextView title = (TextView) convertView.findViewById(R.id.title); + title.setText(printerInfo.getLabel()); + + try { + TextView subtitle = (TextView) + convertView.findViewById(R.id.subtitle); + PackageManager pm = getPackageManager(); + PackageInfo packageInfo = pm.getPackageInfo( + printerInfo.getId().getService().getPackageName(), 0); + subtitle.setText(packageInfo.applicationInfo.loadLabel(pm)); + subtitle.setVisibility(View.VISIBLE); + } catch (NameNotFoundException nnfe) { + /* ignore */ + } + + return convertView; + } + } + } + + private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { + private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1; + private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2; + + private Handler mHandler; + private Editor mEditor; + + @SuppressWarnings("unchecked") + public PrinterDiscoveryObserver(Editor editor, Looper looper) { + mEditor = editor; + mHandler = new Handler(looper, null, true) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ADD_DICOVERED_PRINTERS: { + List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; + mEditor.addPrinters(printers); + } break; + case MESSAGE_REMOVE_DICOVERED_PRINTERS: { + List<PrinterId> printerIds = (List<PrinterId>) message.obj; + mEditor.removePrinters(printerIds); + } break; + } + } + }; + } + + @Override + public void addDiscoveredPrinters(List<PrinterInfo> printers) { + synchronized (this) { + if (mHandler != null) { + mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers) + .sendToTarget(); + } + } + } + + @Override + public void removeDiscoveredPrinters(List<PrinterId> printers) { + synchronized (this) { + if (mHandler != null) { + mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers) + .sendToTarget(); + } + } + } + + public void destroy() { + synchronized (this) { + mHandler = null; + mEditor = null; + } + } + } + + /** + * An instance of this class class is intended to be the first focusable + * in a layout to which the system automatically gives focus. It performs + * some voodoo to avoid the first tap on it to start an edit mode, rather + * to bring up the IME, i.e. to get the behavior as if the view was not + * focused. + */ + public static final class CustomEditText extends EditText { + private boolean mClickedBeforeFocus; + private CharSequence mError; + + public CustomEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean performClick() { + super.performClick(); + if (isFocused() && !mClickedBeforeFocus) { + clearFocus(); + requestFocus(); + } + mClickedBeforeFocus = true; + return true; + } + + @Override + public CharSequence getError() { + return mError; + } + + @Override + public void setError(CharSequence error, Drawable icon) { + setCompoundDrawables(null, null, icon, null); + mError = error; + } + + protected void onFocusChanged(boolean gainFocus, int direction, + Rect previouslyFocusedRect) { + if (!gainFocus) { + mClickedBeforeFocus = false; + } + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + } + } + + private static final class Document { + public PrintDocumentInfo info; + public PageRange[] pages; + } + + private static final class PageRangeUtils { + + private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() { + @Override + public int compare(PageRange lhs, PageRange rhs) { + return lhs.getStart() - rhs.getStart(); + } + }; + + private PageRangeUtils() { + throw new UnsupportedOperationException(); + } + + public static boolean contains(PageRange[] ourPageRanges, PageRange[] otherPageRanges) { + if (ourPageRanges == null || otherPageRanges == null) { + return false; + } + + otherPageRanges = normalize(otherPageRanges); + + int otherPageIdx = 0; + final int myPageCount = ourPageRanges.length; + final int otherPageCount = otherPageRanges.length; + for (int i= 0; i < myPageCount; i++) { + PageRange myPage = ourPageRanges[i]; + for (; otherPageIdx < otherPageCount; otherPageIdx++) { + PageRange otherPage = otherPageRanges[otherPageIdx]; + if (otherPage.getStart() > myPage.getStart()) { + break; + } + if ((otherPage.getStart() < myPage.getStart() + && otherPage.getEnd() > myPage.getStart()) + || (otherPage.getEnd() > myPage.getEnd() + && otherPage.getStart() < myPage.getEnd()) + || (otherPage.getEnd() < myPage.getStart())) { + return false; + } + } + } + if (otherPageIdx < otherPageCount) { + return false; + } + return true; + } + + public static PageRange[] normalize(PageRange[] pageRanges) { + if (pageRanges == null) { + return null; + } + final int oldPageCount = pageRanges.length; + if (oldPageCount <= 1) { + return pageRanges; + } + Arrays.sort(pageRanges, sComparator); + int newRangeCount = 0; + for (int i = 0; i < oldPageCount - 1; i++) { + newRangeCount++; + PageRange currentRange = pageRanges[i]; + PageRange nextRange = pageRanges[i + 1]; + if (currentRange.getEnd() >= nextRange.getStart()) { + newRangeCount--; + pageRanges[i] = null; + pageRanges[i + 1] = new PageRange(currentRange.getStart(), + nextRange.getEnd()); + } + } + if (newRangeCount == oldPageCount) { + return pageRanges; + } + return Arrays.copyOfRange(pageRanges, oldPageCount - newRangeCount, + oldPageCount - 1); + } + + public static void offsetStart(PageRange[] pageRanges, int offset) { + if (offset == 0) { + return; + } + final int pageRangeCount = pageRanges.length; + for (int i = 0; i < pageRangeCount; i++) { + final int start = pageRanges[i].getStart() + offset; + final int end = pageRanges[i].getEnd() + offset; + pageRanges[i] = new PageRange(start, end); + } } } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java index 53ae145..cef4341 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java @@ -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; + } + } + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 26d2a33..5ff2aa6 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -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 diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java index 25bb37c..4006a5a 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -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); + try { + mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling layout()", re); } - 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(); - } + public void write(final PageRange[] pages, final IWriteResultCallback callback, + final int sequence) { + if (DEBUG) { + Log.i(LOG_TAG, "write()"); } - } - - public void finish() { - QueuedAsyncTask task = new QueuedAsyncTask() { + 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; - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error reading file", re); - mState = STATE_FAILED; - } - return null; - } - - @Override - public void cancel() { - /* cannot be cancelled */ - } - }; - synchronized (mLock) { - mTaskQueue.add(task); - } - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); - } - - private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> { - public void cancel() { - super.cancel(true); - } - } - - 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); - } - } - }; + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + source = pipe[0]; + sink = pipe[1]; - private ICancellationSignal mCancellationSignal; + in = new FileInputStream(source.getFileDescriptor()); + out = new FileOutputStream(mFile); - public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes, - LayoutResultCallback callback, Bundle metadata) { - mOldAttributes = oldAttributes; - mNewAttributes = newAttributes; - mCallback = callback; - mMetadata = metadata; - } + // Async call to initiate the other process writing the data. + mRemoteInterface.write(pages, sink, callback, sequence); - @Override - public void cancel() { - synchronized (mLock) { - throwIfCancelledLocked(); - cancelSignalQuietlyLocked(); - } - super.cancel(); - } + // Close the source. It is now held by the client. + sink.close(); + sink = null; - @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) { + // Read the data. + final byte[] buffer = new byte[8192]; while (true) { - if (mState == STATE_LAYOUT_COMPLETED) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { break; } - try { - mLock.wait(); - } catch (InterruptedException ie) { - /* ignore */ - } + out.write(buffer, 0, readByteCount); } - } - } 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(); + 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; } - } - - public void notifyLayoutFailedQuietly() { - try { - mILayoutResultCallback.onLayoutFailed(null); - } catch (RemoteException re) { - /* ignore */ - } - } - - private void throwIfCancelledLocked() { - if (isCancelled()) { - throw new IllegalStateException("Already cancelled"); - } - } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); } - 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 */ - } + public void finish() { + if (DEBUG) { + Log.i(LOG_TAG, "finish()"); } - - private void throwIfCancelledLocked() { - if (isCancelled()) { - throw new IllegalStateException("Already cancelled"); - } + try { + mRemoteInterface.finish(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling finish()", re); } } } diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java index 203bc86..a8f8566 100644 --- a/services/java/com/android/server/print/RemotePrintService.java +++ b/services/java/com/android/server/print/RemotePrintService.java @@ -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; } } } diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java index 6445c2e..ded410b 100644 --- a/services/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/java/com/android/server/print/RemotePrintSpooler.java @@ -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() { |