From 85b1f883056a1d74473fd9ce774948878f389ab6 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Wed, 24 Jul 2013 17:00:06 -0700 Subject: [PATCH] Iteration on the print sub-system. 1. API changes: Moved copies API from PrintAttributes to PrintJobInfo; Changed the PageRange list to an array in PrintDocumentAdapter#onWrite; Added onCancelled method to the layout and write callbacks. 2. Refactored the serialization of remote layout and write commands. Now the commands are serialized by the code in the client instead in the spooler. The benefit is simple code since the client has to do a serialization to delegate to the main thread anyway. The increased IPC found is fine since these calls are quite unfrequent. 3. Removed an unused file: IPrintSpoolerObserver.aidl 4. Added equals and hasCode implementation to PageRange, PrintAttributes, MediaSize, Resolution, Margins, Tray, PrintDocumentInfo. 5. Added shortcut path for query APIs on PrintJob that return cached values if the print job is in a uncuttable state, i.e. completed or cancelled. Failed print jobs can be restarted. 6. PrintJobInfo was not properly serialized. 7. Updated the look of the print dialog to be stable if there is and there isn't currently selected printer. 8. PrintJobCOnfigActivity now calls onLayout on every print attributes change but requests a write only on print preview or print button press. Also if the layout did not change the content and it is already written no subsequent call is made. Also if the selected pages change and we already have them no subsequent call to write is made. Also the app is called with print preview attribute set when performing layout and with it cleared after the print button is pressed. A lot of changes making sure that only valid actions are enabled in the activity (looks like a dialog) at a given time frame. The print job config activity is also hidden after we got all the data, i.e. layout and write are done. 9. The callback from the print spooler to the system are scheduled via messages to avoid lock being held during the call. It was hard to guarantee that since a method holding a lock may be calling one that would like to release the lock at some point to make the callbacks. 10. Print spooler state is persisted only if something changes in a completed print job, i.e. not one that is being constructed due the print job config dialog. 11. Fixed a potential race in the RemotePrintSpooler where it was possible that a client that got a handle to the remote spooler calls into an unbound spooler. E.g: the client gets the remote interface with a lock held, now the client releases the lock to avoid IPC with a lock, during the IPC scheduling the spooler has notified the system that it is done and the system unbinds from it, now the client's IPC is made to a spooler that is disconnected. Change-Id: Ie9c42255940a27ecaed21a4d326a663a4788ac9d --- api/current.txt | 9 +- .../android/print/FileDocumentAdapter.java | 8 +- .../android/print/ILayoutResultCallback.aidl | 6 +- .../android/print/IPrintDocumentAdapter.aidl | 6 +- core/java/android/print/IPrintManager.aidl | 1 - .../android/print/IPrintSpoolerCallbacks.aidl | 2 +- .../android/print/IPrintSpoolerObserver.aidl | 31 - .../android/print/IWriteResultCallback.aidl | 6 +- core/java/android/print/PageRange.java | 32 +- core/java/android/print/PrintAttributes.java | 295 +++- .../android/print/PrintDocumentAdapter.java | 30 +- .../java/android/print/PrintDocumentInfo.java | 30 + core/java/android/print/PrintJob.java | 13 +- core/java/android/print/PrintJobInfo.java | 42 +- core/java/android/print/PrintManager.java | 216 +-- core/java/android/print/PrinterInfo.java | 5 - core/java/android/printservice/PrintJob.java | 12 + .../res/layout/print_job_config_activity.xml | 28 +- packages/PrintSpooler/res/values/strings.xml | 7 +- .../printspooler/PrintJobConfigActivity.java | 1305 +++++++++++------ .../android/printspooler/PrintSpooler.java | 369 +++-- .../printspooler/PrintSpoolerService.java | 5 +- .../RemotePrintDocumentAdapter.java | 494 +------ .../server/print/RemotePrintService.java | 20 +- .../server/print/RemotePrintSpooler.java | 77 +- 25 files changed, 1800 insertions(+), 1249 deletions(-) delete mode 100644 core/java/android/print/IPrintSpoolerObserver.aidl diff --git a/api/current.txt b/api/current.txt index 8c0211a54781e..52bdf68b6451c 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, 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); + 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 c7011f427a600..d642a61fa6a2f 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 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 pages = new ArrayList(); - 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 e4d79f3057ba0..43b8c30945176 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 04da15721c830..b12c922cca6e3 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 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 a466e741bc90d..37ae2ca8b033b 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 7912964efecca..51b5439e64016 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 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 7b8f40e4ef107..0000000000000 --- 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 d5428b145aff0..8281c4e01c659 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 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 9257a04f4f0c7..ba455f622810c 100644 --- a/core/java/android/print/PageRange.java +++ b/core/java/android/print/PageRange.java @@ -92,9 +92,39 @@ public final class PageRange implements Parcelable { parcel.writeInt(mEnd); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mEnd; + result = prime * result + mStart; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PageRange other = (PageRange) obj; + if (mEnd != other.mEnd) { + return false; + } + if (mStart != other.mStart) { + return false; + } + return true; + } + @Override public String toString() { - if (this == ALL_PAGES) { + if (mStart == 0 && mEnd == Integer.MAX_VALUE) { return "PageRange[]"; } StringBuilder builder = new StringBuilder(); diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index 87d75c0dfbeed..911e3803d6485 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; } /** @@ -953,6 +1020,44 @@ public final class PrintAttributes implements Parcelable { parcel.readInt()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mId == null) ? 0 : mId.hashCode()); + result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode()); + result = prime * result + mWidthMils; + result = prime * result + mHeightMils; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MediaSize other = (MediaSize) obj; + if (!TextUtils.equals(mId, other.mId)) { + return false; + } + if (!TextUtils.equals(mLabel, other.mLabel)) { + return false; + } + if (mWidthMils != other.mWidthMils) { + return false; + } + if (mHeightMils != other.mHeightMils) { + return false; + } + return true; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -1060,6 +1165,44 @@ public final class PrintAttributes implements Parcelable { parcel.readInt()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mId == null) ? 0 : mId.hashCode()); + result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode()); + result = prime * result + mHorizontalDpi; + result = prime * result + mVerticalDpi; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Resolution other = (Resolution) obj; + if (!TextUtils.equals(mId, other.mId)) { + return false; + } + if (!TextUtils.equals(mLabel, other.mLabel)) { + return false; + } + if (mHorizontalDpi != other.mHorizontalDpi) { + return false; + } + if (mVerticalDpi != other.mVerticalDpi) { + return false; + } + return true; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -1165,6 +1308,44 @@ public final class PrintAttributes implements Parcelable { parcel.readInt()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mBottomMils; + result = prime * result + mLeftMils; + result = prime * result + mRightMils; + result = prime * result + mTopMils; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Margins other = (Margins) obj; + if (mBottomMils != other.mBottomMils) { + return false; + } + if (mLeftMils != other.mLeftMils) { + return false; + } + if (mRightMils != other.mRightMils) { + return false; + } + if (mTopMils != other.mTopMils) { + return false; + } + return true; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -1234,6 +1415,36 @@ public final class PrintAttributes implements Parcelable { parcel.readCharSequence()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mId == null) ? 0 : mId.hashCode()); + result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Tray other = (Tray) obj; + if (!TextUtils.equals(mId, other.mId)) { + return false; + } + if (!TextUtils.equals(mLabel, other.mLabel)) { + return false; + } + return true; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -1246,21 +1457,6 @@ public final class PrintAttributes implements Parcelable { } } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("PrintAttributes{"); - builder.append("mediaSize: ").append(mMediaSize); - builder.append(", resolution: ").append(mResolution); - builder.append(", margins: ").append(mMargins); - builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode)); - builder.append(", colorMode: ").append(colorModeToString(mColorMode)); - builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode)); - builder.append(", orientation: ").append(orientationToString(mOrientation)); - builder.append(", copies: ").append(mCopies); - return builder.toString(); - } - private static String duplexModeToString(int duplexMode) { switch (duplexMode) { case DUPLEX_MODE_NONE: { @@ -1412,7 +1608,7 @@ public final class PrintAttributes implements Parcelable { * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE */ public Builder setDuplexMode(int duplexMode) { - if (Integer.bitCount(duplexMode) != 1) { + if (Integer.bitCount(duplexMode) > 1) { throw new IllegalArgumentException("can specify at most one duplexMode bit."); } mAttributes.setDuplexMode(duplexMode); @@ -1470,17 +1666,6 @@ public final class PrintAttributes implements Parcelable { return this; } - /** - * Sets the number of copies. - * - * @param copyCount A greater or equal to zero copy count. - * @return This builder. - */ - public Builder setCopyCount(int copyCount) { - mAttributes.setCopies(copyCount); - return this; - } - /** * Creates a new {@link PrintAttributes} instance. * diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java index 1f83a451a8619..d3202262bbd5c 100644 --- a/core/java/android/print/PrintDocumentAdapter.java +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -41,7 +41,7 @@ import java.util.List; *
  • * 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. *
  • *
  • @@ -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. *

    * - * @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 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 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 7d42b3a2e4e9c..29e8e7ce95661 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -110,6 +110,36 @@ public final class PrintDocumentInfo implements Parcelable { parcel.writeInt(mContentType); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mContentType; + result = prime * result + mPageCount; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PrintDocumentInfo other = (PrintDocumentInfo) obj; + if (mContentType != other.mContentType) { + return false; + } + if (mPageCount != other.mPageCount) { + return false; + } + return true; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java index a5e0b796802a4..de28bd3c82256 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 97384d98cf1fc..39546f314247a 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); @@ -309,6 +321,29 @@ public final class PrintJobInfo implements Parcelable { mTag = tag; } + /** + * Gets the number of copies. + * + * @return The number of copies or zero if not set. + */ + public int getCopies() { + return mCopies; + } + + /** + * Sets the number of copies. + * + * @param copyCount The number of copies. + * + * @hide + */ + public void setCopies(int copyCount) { + if (copyCount < 1) { + throw new IllegalArgumentException("Copies must be more than one."); + } + mCopies = copyCount; + } + /** * Gets the included pages. * @@ -385,6 +420,7 @@ public final class PrintJobInfo implements Parcelable { parcel.writeInt(mAppId); parcel.writeInt(mUserId); parcel.writeString(mTag); + parcel.writeInt(mCopies); if (mPageRanges != null) { parcel.writeInt(1); parcel.writeParcelableArray(mPageRanges, flags); @@ -413,10 +449,14 @@ public final class PrintJobInfo implements Parcelable { builder.append(", id: ").append(mId); builder.append(", status: ").append(stateToString(mState)); builder.append(", printer: " + mPrinterId); + builder.append(", tag: ").append(mTag); + builder.append(", copies: ").append(mCopies); builder.append(", attributes: " + (mAttributes != null ? mAttributes.toString() : null)); builder.append(", documentInfo: " + (mDocumentInfo != null ? mDocumentInfo.toString() : null)); + builder.append(", pages: " + (mPageRanges != null + ? Arrays.toString(mPageRanges) : null)); builder.append("}"); return builder.toString(); } diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index f9f53f60c433e..9e8cfad8a73de 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 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 pages = (List) 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 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 da3b6bc27a16f..c0daa6eb458db 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 80530a7cb8d69..0ac5a133b5d30 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 32bc15aea1649..a4105ea0ed455 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"> @@ -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"> + android:minWidth="150dip" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:minWidth="150dip" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:minWidth="150dip" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:minWidth="150dip" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:visibility="gone" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:background="?android:attr/selectableItemBackground" + android:minHeight="?android:attr/listPreferredItemHeight"> + android:background="?android:attr/selectableItemBackground" + android:minHeight="?android:attr/listPreferredItemHeight"> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 27540d7d68380..176269364ef85 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + Print Spooler @@ -38,7 +38,7 @@ ORIENTATION - PAGES + PAGES (%1$s) e.g. 1–5, 8 @@ -52,6 +52,9 @@ Printing app crashed + + unknown + diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 1e1cc24f263ec..86c4f372dc041 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); - mHandler = new MyHandler(Looper.getMainLooper()); + 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); + } + + 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() { - super.onPause(); - mPrintSpooler.stopPrinterDiscovery(); - notifyPrintableFinishIfNeeded(); - } - - private void notifyPrintableStartIfNeeded() { - if (mEditor.getCurrentPrinter() == null - || mStarted) { - return; - } - mStarted = true; - mRemotePrintAdapter.start(); - } - - private void updatePrintableContentIfNeeded() { - if (!mStarted) { - return; - } - - 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(); - } - - @Override - public void onLayoutFailed(CharSequence error) { - mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget(); - } - }, new Bundle()); - } - - private void handleOnLayoutFinished(PrintDocumentInfo info, boolean changed) { - mPrintDocumentInfo = info; - - mEditor.updateUiIfNeeded(); - - // TODO: Handle the case of unchanged content - mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info); - - // TODO: Implement page selector. - final List pages = new ArrayList(); - pages.add(PageRange.ALL_PAGES); - - mRemotePrintAdapter.write(pages, new WriteResultCallback() { - @Override - public void onWriteFinished(List pages) { - mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget(); - } - - @Override - public void onWriteFailed(CharSequence error) { - mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget(); - } - }); - } - - private void handleOnLayoutFailed(CharSequence error) { - Log.e(LOG_TAG, "Error during layout: " + error); - finishActivity(Activity.RESULT_CANCELED); - } - - private void handleOnWriteFinished(List pages) { - // TODO: Now we have to allow the preview button - mEditor.updatePrintPreview(mRemotePrintAdapter.getFile()); - } - - private void handleOnWriteFailed(CharSequence error) { - Log.e(LOG_TAG, "Error write layout: " + error); - finishActivity(Activity.RESULT_CANCELED); - } - - private void notifyPrintableFinishIfNeeded() { - if (!mStarted) { - return; - } - - if (!mPrintConfirmed) { - mRemotePrintAdapter.cancel(); - } - 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, + mSpooler.stopPrinterDiscovery(); + mPrinterDiscoveryObserver.destroy(); + mPrinterDiscoveryObserver = null; + if (mController.isCancelled() || mController.isFailed()) { + mSpooler.setPrintJobState(mPrintJobId, PrintJobInfo.STATE_CANCELED); - return; - } - - // Update the print job's printer. - mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printer.getId()); - - // Update the print job's status. - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_QUEUED); - - if (DEBUG) { - if (mPrintConfirmed) { - File file = mRemotePrintAdapter.getFile(); - if (file.exists()) { - new ViewSpooledFileAsyncTask(file).executeOnExecutor( - AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } 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(); } - private boolean hasPdfViewer() { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType("application/pdf"); - return !getPackageManager().queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY).isEmpty(); - } - - // Caution: Use this only for debugging - private final class ViewSpooledFileAsyncTask extends AsyncTask { - - private final File mFile; - - public ViewSpooledFileAsyncTask(File file) { - mFile = file; - } - - @Override - protected Void doInBackground(Void... params) { - mFile.setExecutable(true, false); - mFile.setWritable(true, false); - mFile.setReadable(true, false); - - 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; + public boolean onTouchEvent(MotionEvent event) { + if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed() + && getWindow().shouldCloseOnTouch(this, event)) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } + mEditor.cancel(); + return true; } + return super.onTouchEvent(event); } - 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; + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + event.startTracking(); + } + return super.onKeyDown(keyCode, event); + } + + 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); + } + + private boolean printAttributesChanged() { + return !mOldPrintAttributes.equals(mCurrPrintAttributes); + } + + private class PrintController { + private final AtomicInteger mRequestCounter = new AtomicInteger(); + + private final RemotePrintDocumentAdapter mRemotePrintAdapter; private final Handler mHandler; - @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 printers = (List) message.obj; - mEditor.addPrinters(printers); - } break; - case MESSAGE_REMOVE_DICOVERED_PRINTERS: { - List printerIds = (List) message.obj; - mEditor.removePrinters(printerIds); - } break; - } + private int mControllerState = CONTROLLER_STATE_INITIALIZED; + + private PageRange[] mRequestedPages; + + private Bundle mMetadata = new Bundle(); + + private final ILayoutResultCallback mILayoutResultCallback = + new ILayoutResultCallback.Stub() { + @Override + 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 addDiscoveredPrinters(List printers) { - mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget(); - } - - @Override - public void removeDiscoveredPrinters(List printers) { - mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget(); - } - } - - private final class SpinnerItem { - final T value; - CharSequence label; - - public SpinnerItem(T value, CharSequence label) { - this.value = value; - this.label = label; - } - - public String toString() { - return label.toString(); - } - } - - /** - * 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; - - 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 void setError(CharSequence error, Drawable icon) { - setCompoundDrawables(null, null, icon, null); - } - - protected void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - if (!gainFocus) { - mClickedBeforeFocus = false; + @Override + public void onLayoutFailed(CharSequence error, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget(); + } } - 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; + 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(); + } + } - public MyHandler(Looper looper) { - super(looper, null, false); + @Override + public void onWriteFailed(CharSequence error, int sequence) { + if (mRequestCounter.get() == sequence) { + mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget(); + } + } + }; + + public PrintController(RemotePrintDocumentAdapter adapter) { + mRemotePrintAdapter = adapter; + mHandler = new MyHandler(Looper.getMainLooper()); } - @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; + public void initialize() { + mControllerState = CONTROLLER_STATE_INITIALIZED; + } - case MSG_ON_LAYOUT_FAILED: { - CharSequence error = (CharSequence) message.obj; - handleOnLayoutFailed(error); - } break; + public void cancel() { + mControllerState = CONTROLLER_STATE_CANCELLED; + } - case MSG_ON_WRITE_FINISHED: { - List pages = (List) message.obj; - handleOnWriteFinished(pages); - } break; + public boolean isCancelled() { + return (mControllerState == CONTROLLER_STATE_CANCELLED); + } - case MSG_ON_WRITE_FAILED: { - CharSequence error = (CharSequence) message.obj; - handleOnWriteFailed(error); - } break; + public boolean isFinished() { + return (mControllerState == CONTROLLER_STATE_FINISHED); + } + + public boolean isFailed() { + return (mControllerState == CONTROLLER_STATE_FAILED); + } + + public boolean hasStarted() { + return mControllerState >= CONTROLLER_STATE_STARTED; + } + + public boolean hasPerformedLayout() { + return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED; + } + + public boolean isWorking() { + return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED + || mControllerState == CONTROLLER_STATE_WRITE_STARTED; + } + + public void start() { + mControllerState = CONTROLLER_STATE_STARTED; + mRemotePrintAdapter.start(); + } + + 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); + + mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW, + !mEditor.isPrintConfirmed()); + + mControllerState = CONTROLLER_STATE_LAYOUT_STARTED; + + mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes, + mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet()); + + mOldPrintAttributes.copyFrom(mCurrPrintAttributes); + } + } + + public void finish() { + mControllerState = CONTROLLER_STATE_FINISHED; + mRemotePrintAdapter.finish(); + } + + private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) { + if (isCancelled()) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } + + mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED; + + // 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(); + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // Request a write of the pages of interest. + mControllerState = CONTROLLER_STATE_WRITE_STARTED; + mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback, + mRequestCounter.incrementAndGet()); + } + + 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(); + } + + private void handleOnWriteFinished(PageRange[] pages) { + if (isCancelled()) { + if (mEditor.isDone()) { + PrintJobConfigActivity.this.finish(); + } + return; + } + + mControllerState = CONTROLLER_STATE_WRITE_COMPLETED; + + // Update which pages we have fetched. + mDocument.pages = PageRangeUtils.normalize(pages); + + 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(); + } + } + + 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 EditText mRangeEditText; + private final TextView mRangeTitle; + private final EditText mRangeEditText; - private Spinner mDestinationSpinner; - public ArrayAdapter> mDestinationSpinnerAdapter; + private final Spinner mDestinationSpinner; + private final ArrayAdapter> mDestinationSpinnerAdapter; - private Spinner mMediaSizeSpinner; - public ArrayAdapter> mMediaSizeSpinnerAdapter; + private final Spinner mMediaSizeSpinner; + private final ArrayAdapter> mMediaSizeSpinnerAdapter; - private Spinner mColorModeSpinner; - public ArrayAdapter> mColorModeSpinnerAdapter; + private final Spinner mColorModeSpinner; + private final ArrayAdapter> mColorModeSpinnerAdapter; - private Spinner mOrientationSpinner; - public ArrayAdapter> mOrientationSpinnerAdapter; + private final Spinner mOrientationSpinner; + private final ArrayAdapter> mOrientationSpinnerAdapter; - private Spinner mRangeOptionsSpinner; - public ArrayAdapter> mRangeOptionsSpinnerAdapter; + private final Spinner mRangeOptionsSpinner; + private final ArrayAdapter> mRangeOptionsSpinnerAdapter; - private Button mPrintPreviewButton; + private final SimpleStringSplitter mStringCommaSplitter = + new SimpleStringSplitter(','); - private Button mPrintButton; + private final Button mPrintPreviewButton; + + 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 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 mediaItem = mMediaSizeSpinnerAdapter.getItem(position); - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.setMediaSize(mediaItem.value); - updatePrintableContentIfNeeded(); + if (!hasErrors()) { + mController.update(); + } } else if (spinner == mColorModeSpinner) { SpinnerItem colorModeItem = mColorModeSpinnerAdapter.getItem(position); - mOldPrintAttributes.copyFrom(mCurrPrintAttributes); mCurrPrintAttributes.setColorMode(colorModeItem.value); - updatePrintableContentIfNeeded(); + if (!hasErrors()) { + mController.update(); + } } else if (spinner == mOrientationSpinner) { SpinnerItem 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); + updateUi(); + + if (hadErrors && !hasErrors() && printAttributesChanged()) { + updateUi(); + } } }; + private int mEditorState; + 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); - } - - 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(); - } - - mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper()); - - bindUi(); - } - - private void bindUi() { // 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>( - 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>( 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( 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 pageRanges = new ArrayList(); + 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 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 { + 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> { + + 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 printers = (List) message.obj; + mEditor.addPrinters(printers); + } break; + case MESSAGE_REMOVE_DICOVERED_PRINTERS: { + List printerIds = (List) message.obj; + mEditor.removePrinters(printerIds); + } break; + } + } + }; + } + + @Override + public void addDiscoveredPrinters(List printers) { + synchronized (this) { + if (mHandler != null) { + mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers) + .sendToTarget(); + } + } + } + + @Override + public void removeDiscoveredPrinters(List 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 sComparator = new Comparator() { + @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 53ae1459ca0af..cef4341f14b2e 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 getPrintJobInfos(ComponentName componentName, int state, int appId) { + List foundPrintJobs = null; synchronized (mLock) { - List 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> activeJobsPerServiceMap = new HashMap>(); - 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() { + @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() { - @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() { + @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 26d2a3329a661..5ff2aa6100f3a 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 25bb37c517106..4006a5a746bc7 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 mTaskQueue = new ArrayList(); + 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 pages, WriteResultCallback callback) { - WriteAsyncTask task = new WriteAsyncTask(pages, callback); - synchronized (mLock) { - mTaskQueue.add(task); - } - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); - } - - public void cancel() { - synchronized (mLock) { - final int taskCount = mTaskQueue.size(); - for (int i = taskCount - 1; i >= 0; i--) { - mTaskQueue.remove(i).cancel(); - } + try { + mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling layout()", re); } } - public void finish() { - QueuedAsyncTask task = new QueuedAsyncTask() { + public void write(final PageRange[] pages, final IWriteResultCallback callback, + final int sequence) { + if (DEBUG) { + Log.i(LOG_TAG, "write()"); + } + new AsyncTask() { @Override protected Void doInBackground(Void... params) { - if (DEBUG) { - Log.i(LOG_TAG, "finish()"); - } - synchronized (mLock) { - if (mState < STATE_START_COMPLETED) { - return null; - } - } + InputStream in = null; + OutputStream out = null; + ParcelFileDescriptor source = null; + ParcelFileDescriptor sink = null; try { - mRemoteInterface.finish(); - synchronized (mLock) { - mState = STATE_FINISH_COMPLETED; + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + source = pipe[0]; + sink = pipe[1]; + + in = new FileInputStream(source.getFileDescriptor()); + out = new FileOutputStream(mFile); + + // Async call to initiate the other process writing the data. + mRemoteInterface.write(pages, sink, callback, sequence); + + // Close the source. It is now held by the client. + sink.close(); + sink = null; + + // Read the data. + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); } } catch (RemoteException re) { - Log.e(LOG_TAG, "Error reading file", re); - mState = STATE_FAILED; + Log.e(LOG_TAG, "Error calling write()", re); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error calling write()", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(sink); + IoUtils.closeQuietly(source); } return null; } - - @Override - public void cancel() { - /* cannot be cancelled */ - } - }; - synchronized (mLock) { - mTaskQueue.add(task); - } - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); } - private abstract class QueuedAsyncTask extends AsyncTask { - public void cancel() { - super.cancel(true); + public void finish() { + if (DEBUG) { + Log.i(LOG_TAG, "finish()"); } - } - - private final class LayoutAsyncTask extends QueuedAsyncTask { - - private final PrintAttributes mOldAttributes; - - private final PrintAttributes mNewAttributes; - - private final LayoutResultCallback mCallback; - - private final Bundle mMetadata; - - private final ILayoutResultCallback mILayoutResultCallback = - new ILayoutResultCallback.Stub() { - @Override - public void onLayoutStarted(ICancellationSignal cancellationSignal) { - if (DEBUG) { - Log.i(LOG_TAG, "onLayoutStarted()"); - } - synchronized (mLock) { - mCancellationSignal = cancellationSignal; - if (isCancelled()) { - cancelSignalQuietlyLocked(); - } - } - } - - @Override - public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { - if (DEBUG) { - Log.i(LOG_TAG, "onLayoutFinished()"); - } - final boolean cancelled; - synchronized (mLock) { - cancelled = isCancelled(); - mCancellationSignal = null; - mState = STATE_LAYOUT_COMPLETED; - mTaskQueue.remove(this); - mLock.notifyAll(); - } - if (!cancelled) { - mCallback.onLayoutFinished(info, changed); - } - } - - @Override - public void onLayoutFailed(CharSequence error) { - if (DEBUG) { - Log.i(LOG_TAG, "onLayoutFailed()"); - } - final boolean cancelled; - synchronized (mLock) { - cancelled = isCancelled(); - mCancellationSignal = null; - mState = STATE_LAYOUT_COMPLETED; - mTaskQueue.remove(this); - mLock.notifyAll(); - } - if (!cancelled) { - mCallback.onLayoutFailed(error); - } - } - }; - - private ICancellationSignal mCancellationSignal; - - public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes, - LayoutResultCallback callback, Bundle metadata) { - mOldAttributes = oldAttributes; - mNewAttributes = newAttributes; - mCallback = callback; - mMetadata = metadata; - } - - @Override - public void cancel() { - synchronized (mLock) { - throwIfCancelledLocked(); - cancelSignalQuietlyLocked(); - } - super.cancel(); - } - - @Override - protected Void doInBackground(Void... params) { - synchronized (mLock) { - if (DEBUG) { - Log.i(LOG_TAG, "layout()"); - } - if (mState != STATE_START_COMPLETED - && mState != STATE_LAYOUT_COMPLETED - && mState != STATE_WRITE_COMPLETED) { - throw new IllegalStateException("Invalid state: " + mState); - } - mState = STATE_LAYOUT_STARTED; - } - try { - mRemoteInterface.layout(mOldAttributes, mNewAttributes, - mILayoutResultCallback, mMetadata); - synchronized (mLock) { - while (true) { - if (mState == STATE_LAYOUT_COMPLETED) { - break; - } - try { - mLock.wait(); - } catch (InterruptedException ie) { - /* ignore */ - } - } - } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error calling layout", re); - mState = STATE_FAILED; - mTaskQueue.remove(this); - notifyLayoutFailedQuietly(); - } - return null; - } - - private void cancelSignalQuietlyLocked() { - if (mCancellationSignal != null) { - try { - mCancellationSignal.cancel(); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error cancelling layout", re); - notifyLayoutFailedQuietly(); - } - } - } - - public void notifyLayoutFailedQuietly() { - try { - mILayoutResultCallback.onLayoutFailed(null); - } catch (RemoteException re) { - /* ignore */ - } - } - - private void throwIfCancelledLocked() { - if (isCancelled()) { - throw new IllegalStateException("Already cancelled"); - } - } - } - - private final class WriteAsyncTask extends QueuedAsyncTask { - - private final List 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 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 pages, WriteResultCallback callback) { - mPages = pages; - mCallback = callback; - } - - @Override - public void cancel() { - synchronized (mLock) { - throwIfCancelledLocked(); - cancelSignalQuietlyLocked(); - mWriteThread.interrupt(); - } - super.cancel(); - } - - @Override - protected Void doInBackground(Void... params) { - if (DEBUG) { - Log.i(LOG_TAG, "write()"); - } - synchronized (mLock) { - if (mState != STATE_LAYOUT_COMPLETED - && mState != STATE_WRITE_COMPLETED) { - throw new IllegalStateException("Invalid state: " + mState); - } - mState = STATE_WRITE_STARTED; - } - InputStream in = null; - OutputStream out = null; - ParcelFileDescriptor source = null; - ParcelFileDescriptor sink = null; - synchronized (mLock) { - mWriteThread = Thread.currentThread(); - } - try { - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - source = pipe[0]; - sink = pipe[1]; - - in = new FileInputStream(source.getFileDescriptor()); - out = new FileOutputStream(mFile); - - // Async call to initiate the other process writing the data. - mRemoteInterface.write(mPages, sink, mIWriteResultCallback); - - // Close the source. It is now held by the client. - sink.close(); - sink = null; - - final byte[] buffer = new byte[8192]; - while (true) { - if (Thread.currentThread().isInterrupted()) { - Thread.currentThread().interrupt(); - break; - } - final int readByteCount = in.read(buffer); - if (readByteCount < 0) { - break; - } - out.write(buffer, 0, readByteCount); - } - synchronized (mLock) { - while (true) { - if (mState == STATE_WRITE_COMPLETED) { - break; - } - try { - mLock.wait(); - } catch (InterruptedException ie) { - /* ignore */ - } - } - } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error writing print document", re); - mState = STATE_FAILED; - mTaskQueue.remove(this); - notifyWriteFailedQuietly(); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "Error writing print document", ioe); - mState = STATE_FAILED; - mTaskQueue.remove(this); - notifyWriteFailedQuietly(); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(sink); - IoUtils.closeQuietly(source); - } - return null; - } - - private void cancelSignalQuietlyLocked() { - if (mCancellationSignal != null) { - try { - mCancellationSignal.cancel(); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error cancelling layout", re); - notifyWriteFailedQuietly(); - } - } - } - - private void notifyWriteFailedQuietly() { - try { - mIWriteResultCallback.onWriteFailed(null); - } catch (RemoteException re) { - /* ignore */ - } - } - - private void throwIfCancelledLocked() { - if (isCancelled()) { - throw new IllegalStateException("Already cancelled"); - } + try { + mRemoteInterface.finish(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling finish()", re); } } } diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java index 203bc866bb21f..a8f856650e6cd 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 6445c2ee98149..ded410b25b5c1 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() {