summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2013-07-24 17:00:06 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2013-07-30 17:15:11 -0700
commit85b1f883056a1d74473fd9ce774948878f389ab6 (patch)
tree7417d396a98766611636e0123102154f60726737
parent0d1daa50f6d180c57f92596501e2e5c0b5ef9997 (diff)
downloadframeworks_base-85b1f883056a1d74473fd9ce774948878f389ab6.zip
frameworks_base-85b1f883056a1d74473fd9ce774948878f389ab6.tar.gz
frameworks_base-85b1f883056a1d74473fd9ce774948878f389ab6.tar.bz2
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
-rw-r--r--api/current.txt9
-rw-r--r--core/java/android/print/FileDocumentAdapter.java8
-rw-r--r--core/java/android/print/ILayoutResultCallback.aidl6
-rw-r--r--core/java/android/print/IPrintDocumentAdapter.aidl6
-rw-r--r--core/java/android/print/IPrintManager.aidl1
-rw-r--r--core/java/android/print/IPrintSpoolerCallbacks.aidl2
-rw-r--r--core/java/android/print/IPrintSpoolerObserver.aidl31
-rw-r--r--core/java/android/print/IWriteResultCallback.aidl6
-rw-r--r--core/java/android/print/PageRange.java32
-rw-r--r--core/java/android/print/PrintAttributes.java295
-rw-r--r--core/java/android/print/PrintDocumentAdapter.java30
-rw-r--r--core/java/android/print/PrintDocumentInfo.java30
-rw-r--r--core/java/android/print/PrintJob.java13
-rw-r--r--core/java/android/print/PrintJobInfo.java42
-rw-r--r--core/java/android/print/PrintManager.java216
-rw-r--r--core/java/android/print/PrinterInfo.java5
-rw-r--r--core/java/android/printservice/PrintJob.java12
-rw-r--r--packages/PrintSpooler/res/layout/print_job_config_activity.xml28
-rw-r--r--packages/PrintSpooler/res/values/strings.xml7
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java1225
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java369
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java5
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java480
-rw-r--r--services/java/com/android/server/print/RemotePrintService.java20
-rw-r--r--services/java/com/android/server/print/RemotePrintSpooler.java77
25 files changed, 1753 insertions, 1202 deletions
diff --git a/api/current.txt b/api/current.txt
index 8c0211a..52bdf68 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18462,7 +18462,6 @@ package android.print {
method public void clear();
method public int describeContents();
method public int getColorMode();
- method public int getCopies();
method public int getDuplexMode();
method public int getFittingMode();
method public android.print.PrintAttributes.Tray getInputTray();
@@ -18488,7 +18487,6 @@ package android.print {
ctor public PrintAttributes.Builder();
method public android.print.PrintAttributes create();
method public android.print.PrintAttributes.Builder setColorMode(int);
- method public android.print.PrintAttributes.Builder setCopyCount(int);
method public android.print.PrintAttributes.Builder setDuplexMode(int);
method public android.print.PrintAttributes.Builder setFittingMode(int);
method public android.print.PrintAttributes.Builder setInputTray(android.print.PrintAttributes.Tray);
@@ -18574,18 +18572,20 @@ package android.print {
method public void onFinish();
method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle);
method public void onStart();
- method public abstract void onWrite(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
+ method public abstract void onWrite(android.print.PageRange[], java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
field public static final java.lang.String METADATA_KEY_PRINT_PREVIEW = "KEY_METADATA_PRINT_PREVIEW";
}
public static abstract class PrintDocumentAdapter.LayoutResultCallback {
+ method public void onLayoutCancelled();
method public void onLayoutFailed(java.lang.CharSequence);
method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean);
}
public static abstract class PrintDocumentAdapter.WriteResultCallback {
+ method public void onWriteCancelled();
method public void onWriteFailed(java.lang.CharSequence);
- method public void onWriteFinished(java.util.List<android.print.PageRange>);
+ method public void onWriteFinished(android.print.PageRange[]);
}
public final class PrintDocumentInfo implements android.os.Parcelable {
@@ -18616,6 +18616,7 @@ package android.print {
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
method public android.print.PrintAttributes getAttributes();
+ method public int getCopies();
method public int getId();
method public java.lang.CharSequence getLabel();
method public android.print.PageRange[] getPages();
diff --git a/core/java/android/print/FileDocumentAdapter.java b/core/java/android/print/FileDocumentAdapter.java
index c7011f4..d642a61 100644
--- a/core/java/android/print/FileDocumentAdapter.java
+++ b/core/java/android/print/FileDocumentAdapter.java
@@ -34,8 +34,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
/**
* Adapter for printing files.
@@ -69,7 +67,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
}
@Override
- public void onWrite(List<PageRange> pages, FileDescriptor destination,
+ public void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback);
mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
@@ -127,9 +125,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
@Override
protected void onPostExecute(Void result) {
- List<PageRange> pages = new ArrayList<PageRange>();
- pages.add(PageRange.ALL_PAGES);
- mResultCallback.onWriteFinished(pages);
+ mResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
}
@Override
diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl
index e4d79f3..43b8c30 100644
--- a/core/java/android/print/ILayoutResultCallback.aidl
+++ b/core/java/android/print/ILayoutResultCallback.aidl
@@ -16,7 +16,6 @@
package android.print;
-import android.os.ICancellationSignal;
import android.print.PrintDocumentInfo;
/**
@@ -25,7 +24,6 @@ import android.print.PrintDocumentInfo;
* @hide
*/
oneway interface ILayoutResultCallback {
- void onLayoutStarted(ICancellationSignal cancellationSignal);
- void onLayoutFinished(in PrintDocumentInfo info, boolean changed);
- void onLayoutFailed(CharSequence error);
+ void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
+ void onLayoutFailed(CharSequence error, int sequence);
}
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 04da157..b12c922 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -31,8 +31,8 @@ import android.print.PrintAttributes;
oneway interface IPrintDocumentAdapter {
void start();
void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes,
- ILayoutResultCallback callback, in Bundle metadata);
- void write(in List<PageRange> pages, in ParcelFileDescriptor fd,
- IWriteResultCallback callback);
+ ILayoutResultCallback callback, in Bundle metadata, int sequence);
+ void write(in PageRange[] pages, in ParcelFileDescriptor fd,
+ IWriteResultCallback callback, int sequence);
void finish();
}
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index a466e74..37ae2ca 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -33,5 +33,4 @@ interface IPrintManager {
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
int appId, int userId);
void cancelPrintJob(int printJobId, int appId, int userId);
-
}
diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl
index 7912964..51b5439 100644
--- a/core/java/android/print/IPrintSpoolerCallbacks.aidl
+++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl
@@ -28,9 +28,9 @@ import java.util.List;
*/
oneway interface IPrintSpoolerCallbacks {
void onGetPrintJobInfosResult(in List<PrintJobInfo> printJob, int sequence);
- void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence);
void onCancelPrintJobResult(boolean canceled, int sequence);
void onSetPrintJobStateResult(boolean success, int sequence);
void onSetPrintJobTagResult(boolean success, int sequence);
+ void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
}
diff --git a/core/java/android/print/IPrintSpoolerObserver.aidl b/core/java/android/print/IPrintSpoolerObserver.aidl
deleted file mode 100644
index 7b8f40e..0000000
--- a/core/java/android/print/IPrintSpoolerObserver.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.print;
-
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for observing the state of the print spooler.
- *
- * @hide
- */
-oneway interface IPrinterDiscoveryObserver {
- void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob);
- void onAllPrintJobsHandled(in ComponentName printService);
- void onAllPrintJobsHandled();
-}
diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl
index d5428b1..8281c4e 100644
--- a/core/java/android/print/IWriteResultCallback.aidl
+++ b/core/java/android/print/IWriteResultCallback.aidl
@@ -16,7 +16,6 @@
package android.print;
-import android.os.ICancellationSignal;
import android.print.PageRange;
/**
@@ -25,7 +24,6 @@ import android.print.PageRange;
* @hide
*/
oneway interface IWriteResultCallback {
- void onWriteStarted(ICancellationSignal cancellationSignal);
- void onWriteFinished(in List<PageRange> pages);
- void onWriteFailed(CharSequence error);
+ void onWriteFinished(in PageRange[] pages, int sequence);
+ void onWriteFailed(CharSequence error, int sequence);
}
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 9257a04..ba455f6 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -93,8 +93,38 @@ public final class PageRange implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mEnd;
+ result = prime * result + mStart;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PageRange other = (PageRange) obj;
+ if (mEnd != other.mEnd) {
+ return false;
+ }
+ if (mStart != other.mStart) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
- if (this == ALL_PAGES) {
+ if (mStart == 0 && mEnd == Integer.MAX_VALUE) {
return "PageRange[<all pages>]";
}
StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 87d75c0..911e380 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -77,7 +77,6 @@ public final class PrintAttributes implements Parcelable {
private int mColorMode;
private int mFittingMode;
private int mOrientation;
- private int mCopies;
PrintAttributes() {
/* hide constructor */
@@ -93,7 +92,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = parcel.readInt();
mFittingMode = parcel.readInt();
mOrientation = parcel.readInt();
- mCopies = parcel.readInt();
}
/**
@@ -302,29 +300,6 @@ public final class PrintAttributes implements Parcelable {
mOrientation = orientation;
}
- /**
- * Gets the number of copies.
- *
- * @return The number of copies or zero if not set.
- */
- public int getCopies() {
- return mCopies;
- }
-
- /**
- * Sets the number of copies.
- *
- * @param copyCount The number of copies.
- *
- * @hide
- */
- public void setCopies(int copyCount) {
- if (copyCount < 1) {
- throw new IllegalArgumentException("Copies must be more than one.");
- }
- mCopies = copyCount;
- }
-
@Override
public void writeToParcel(Parcel parcel, int flags) {
if (mMediaSize != null) {
@@ -361,7 +336,6 @@ public final class PrintAttributes implements Parcelable {
parcel.writeInt(mColorMode);
parcel.writeInt(mFittingMode);
parcel.writeInt(mOrientation);
- parcel.writeInt(mCopies);
}
@Override
@@ -369,6 +343,101 @@ public final class PrintAttributes implements Parcelable {
return 0;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mColorMode;
+ result = prime * result + mDuplexMode;
+ result = prime * result + mFittingMode;
+ result = prime * result + mOrientation;
+ result = prime * result + ((mInputTray == null) ? 0 : mInputTray.hashCode());
+ result = prime * result + ((mMargins == null) ? 0 : mMargins.hashCode());
+ result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
+ result = prime * result + ((mOutputTray == null) ? 0 : mOutputTray.hashCode());
+ result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrintAttributes other = (PrintAttributes) obj;
+ if (mColorMode != other.mColorMode) {
+ return false;
+ }
+ if (mDuplexMode != other.mDuplexMode) {
+ return false;
+ }
+ if (mFittingMode != other.mFittingMode) {
+ return false;
+ }
+ if (mOrientation != other.mOrientation) {
+ return false;
+ }
+ if (mInputTray == null) {
+ if (other.mInputTray != null) {
+ return false;
+ }
+ } else if (!mInputTray.equals(other.mInputTray)) {
+ return false;
+ }
+ if (mOutputTray == null) {
+ if (other.mOutputTray != null) {
+ return false;
+ }
+ } else if (!mOutputTray.equals(other.mOutputTray)) {
+ return false;
+ }
+ if (mMargins == null) {
+ if (other.mMargins != null) {
+ return false;
+ }
+ } else if (!mMargins.equals(other.mMargins)) {
+ return false;
+ }
+ if (mMediaSize == null) {
+ if (other.mMediaSize != null) {
+ return false;
+ }
+ } else if (!mMediaSize.equals(other.mMediaSize)) {
+ return false;
+ }
+ if (mResolution == null) {
+ if (other.mResolution != null) {
+ return false;
+ }
+ } else if (!mResolution.equals(other.mResolution)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PrintAttributes{");
+ builder.append("mediaSize: ").append(mMediaSize);
+ builder.append(", resolution: ").append(mResolution);
+ builder.append(", margins: ").append(mMargins);
+ builder.append(", inputTray: ").append(mInputTray);
+ builder.append(", outputTray: ").append(mOutputTray);
+ builder.append(", colorMode: ").append(colorModeToString(mColorMode));
+ builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
+ builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
+ builder.append(", orientation: ").append(orientationToString(mOrientation));
+ builder.append("}");
+ return builder.toString();
+ }
+
/** hide */
public void clear() {
mMediaSize = null;
@@ -380,7 +449,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = 0;
mFittingMode = 0;
mOrientation = 0;
- mCopies = 0;
}
/**
@@ -396,7 +464,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = other.mColorMode;
mFittingMode = other.mFittingMode;
mOrientation = other.mOrientation;
- mCopies = other.mCopies;
}
/**
@@ -954,6 +1021,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ result = prime * result + mWidthMils;
+ result = prime * result + mHeightMils;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MediaSize other = (MediaSize) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ if (mWidthMils != other.mWidthMils) {
+ return false;
+ }
+ if (mHeightMils != other.mHeightMils) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MediaSize{");
@@ -1061,6 +1166,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ result = prime * result + mHorizontalDpi;
+ result = prime * result + mVerticalDpi;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Resolution other = (Resolution) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ if (mHorizontalDpi != other.mHorizontalDpi) {
+ return false;
+ }
+ if (mVerticalDpi != other.mVerticalDpi) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Resolution{");
@@ -1166,6 +1309,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mBottomMils;
+ result = prime * result + mLeftMils;
+ result = prime * result + mRightMils;
+ result = prime * result + mTopMils;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Margins other = (Margins) obj;
+ if (mBottomMils != other.mBottomMils) {
+ return false;
+ }
+ if (mLeftMils != other.mLeftMils) {
+ return false;
+ }
+ if (mRightMils != other.mRightMils) {
+ return false;
+ }
+ if (mTopMils != other.mTopMils) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Margins{");
@@ -1235,6 +1416,36 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Tray other = (Tray) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Tray{");
@@ -1246,21 +1457,6 @@ public final class PrintAttributes implements Parcelable {
}
}
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("PrintAttributes{");
- builder.append("mediaSize: ").append(mMediaSize);
- builder.append(", resolution: ").append(mResolution);
- builder.append(", margins: ").append(mMargins);
- builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
- builder.append(", colorMode: ").append(colorModeToString(mColorMode));
- builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
- builder.append(", orientation: ").append(orientationToString(mOrientation));
- builder.append(", copies: ").append(mCopies);
- return builder.toString();
- }
-
private static String duplexModeToString(int duplexMode) {
switch (duplexMode) {
case DUPLEX_MODE_NONE: {
@@ -1412,7 +1608,7 @@ public final class PrintAttributes implements Parcelable {
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
*/
public Builder setDuplexMode(int duplexMode) {
- if (Integer.bitCount(duplexMode) != 1) {
+ if (Integer.bitCount(duplexMode) > 1) {
throw new IllegalArgumentException("can specify at most one duplexMode bit.");
}
mAttributes.setDuplexMode(duplexMode);
@@ -1471,17 +1667,6 @@ public final class PrintAttributes implements Parcelable {
}
/**
- * Sets the number of copies.
- *
- * @param copyCount A greater or equal to zero copy count.
- * @return This builder.
- */
- public Builder setCopyCount(int copyCount) {
- mAttributes.setCopies(copyCount);
- return this;
- }
-
- /**
* Creates a new {@link PrintAttributes} instance.
*
* @return The new instance.
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index 1f83a45..d320226 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -41,7 +41,7 @@ import java.util.List;
* <li>
* After every call to {@link #onLayout(PrintAttributes, PrintAttributes,
* CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to
- * {@link #onWrite(List, FileDescriptor, CancellationSignal, WriteResultCallback)}
+ * {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)}
* asking you to write a PDF file with the content for specific pages.
* </li>
* <li>
@@ -64,7 +64,7 @@ import java.util.List;
* PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on
* the UI thread (assuming onStart initializes resources needed for layout).
* This will ensure that the UI does not change while you are laying out the
- * printed content. Then you can handle {@link #onWrite(List, FileDescriptor,
+ * printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor,
* CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another
* thread. This will ensure that the UI is frozen for the minimal amount of
* time. Also this assumes that you will generate the printed content in
@@ -141,7 +141,7 @@ public abstract class PrintDocumentAdapter {
* made on the main thread.
* </p>
*
- * @param pages The pages whose content to print.
+ * @param pages The pages whose content to print - non-overlapping in ascending order.
* @param destination The destination file descriptor to which to write.
* @param cancellationSignal Signal for observing cancel writing requests.
* @param callback Callback to inform the system for the write result.
@@ -149,7 +149,7 @@ public abstract class PrintDocumentAdapter {
* @see WriteResultCallback
* @see CancellationSignal
*/
- public abstract void onWrite(List<PageRange> pages, FileDescriptor destination,
+ public abstract void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback);
/**
@@ -163,7 +163,7 @@ public abstract class PrintDocumentAdapter {
/**
* Base class for implementing a callback for the result of {@link
- * PrintDocumentAdapter#onWrite(List, FileDescriptor, CancellationSignal,
+ * PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal,
* WriteResultCallback)}.
*/
public static abstract class WriteResultCallback {
@@ -178,9 +178,9 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that all the data was written.
*
- * @param pages The pages that were written.
+ * @param pages The pages that were written. Cannot be null or empty.
*/
- public void onWriteFinished(List<PageRange> pages) {
+ public void onWriteFinished(PageRange[] pages) {
/* do nothing - stub */
}
@@ -192,6 +192,13 @@ public abstract class PrintDocumentAdapter {
public void onWriteFailed(CharSequence error) {
/* do nothing - stub */
}
+
+ /**
+ * Notifies that write was cancelled as a result of a cancellation request.
+ */
+ public void onWriteCancelled() {
+ /* do nothing - stub */
+ }
}
/**
@@ -211,7 +218,7 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that the layout finished and whether the content changed.
*
- * @param info An info object describing the document.
+ * @param info An info object describing the document. Cannot be null.
* @param changed Whether the layout changed.
*
* @see PrintDocumentInfo
@@ -228,5 +235,12 @@ public abstract class PrintDocumentAdapter {
public void onLayoutFailed(CharSequence error) {
/* do nothing - stub */
}
+
+ /**
+ * Notifies that layout was cancelled as a result of a cancellation request.
+ */
+ public void onLayoutCancelled() {
+ /* do nothing - stub */
+ }
}
}
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 7d42b3a..29e8e7c 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -111,6 +111,36 @@ public final class PrintDocumentInfo implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mContentType;
+ result = prime * result + mPageCount;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrintDocumentInfo other = (PrintDocumentInfo) obj;
+ if (mContentType != other.mContentType) {
+ return false;
+ }
+ if (mPageCount != other.mPageCount) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintDocumentInfo{");
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index a5e0b79..de28bd3 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -55,6 +55,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ if (isInImmutableState()) {
+ return mCachedInfo;
+ }
PrintJobInfo info = mPrintManager.getPrintJobInfo(mId);
if (info != null) {
mCachedInfo = info;
@@ -66,7 +69,15 @@ public final class PrintJob {
* Cancels this print job.
*/
public void cancel() {
- mPrintManager.cancelPrintJob(mId);
+ if (!isInImmutableState()) {
+ mPrintManager.cancelPrintJob(mId);
+ }
+ }
+
+ private boolean isInImmutableState() {
+ final int state = mCachedInfo.getState();
+ return state == PrintJobInfo.STATE_COMPLETED
+ || state == PrintJobInfo.STATE_CANCELED;
}
@Override
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 97384d9..39546f3 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -19,6 +19,8 @@ package android.print;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* This class represents the description of a print job.
*/
@@ -119,6 +121,9 @@ public final class PrintJobInfo implements Parcelable {
/** Optional tag assigned by a print service.*/
private String mTag;
+ /** How many copies to print. */
+ private int mCopies;
+
/** The pages to print */
private PageRange[] mPageRanges;
@@ -142,6 +147,8 @@ public final class PrintJobInfo implements Parcelable {
mAppId = other.mAppId;
mUserId = other.mUserId;
mTag = other.mTag;
+ mCopies = other.mCopies;
+ mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
}
@@ -154,8 +161,13 @@ public final class PrintJobInfo implements Parcelable {
mAppId = parcel.readInt();
mUserId = parcel.readInt();
mTag = parcel.readString();
+ mCopies = parcel.readInt();
if (parcel.readInt() == 1) {
- mPageRanges = (PageRange[]) parcel.readParcelableArray(null);
+ Parcelable[] parcelables = parcel.readParcelableArray(null);
+ mPageRanges = new PageRange[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ mPageRanges[i] = (PageRange) parcelables[i];
+ }
}
if (parcel.readInt() == 1) {
mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel);
@@ -310,6 +322,29 @@ public final class PrintJobInfo implements Parcelable {
}
/**
+ * Gets the number of copies.
+ *
+ * @return The number of copies or zero if not set.
+ */
+ public int getCopies() {
+ return mCopies;
+ }
+
+ /**
+ * Sets the number of copies.
+ *
+ * @param copyCount The number of copies.
+ *
+ * @hide
+ */
+ public void setCopies(int copyCount) {
+ if (copyCount < 1) {
+ throw new IllegalArgumentException("Copies must be more than one.");
+ }
+ mCopies = copyCount;
+ }
+
+ /**
* Gets the included pages.
*
* @return The included pages or <code>null</code> if not set.
@@ -385,6 +420,7 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeInt(mAppId);
parcel.writeInt(mUserId);
parcel.writeString(mTag);
+ parcel.writeInt(mCopies);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
@@ -413,10 +449,14 @@ public final class PrintJobInfo implements Parcelable {
builder.append(", id: ").append(mId);
builder.append(", status: ").append(stateToString(mState));
builder.append(", printer: " + mPrinterId);
+ builder.append(", tag: ").append(mTag);
+ builder.append(", copies: ").append(mCopies);
builder.append(", attributes: " + (mAttributes != null
? mAttributes.toString() : null));
builder.append(", documentInfo: " + (mDocumentInfo != null
? mDocumentInfo.toString() : null));
+ builder.append(", pages: " + (mPageRanges != null
+ ? Arrays.toString(mPageRanges) : null));
builder.append("}");
return builder.toString();
}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index f9f53f6..9e8cfad 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -22,7 +22,6 @@ import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -223,6 +222,11 @@ public final class PrintManager {
}
private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
+
+ private final Object mLock = new Object();
+
+ private CancellationSignal mLayoutOrWriteCancellation;
+
private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
private Handler mHandler; // Strong reference OK - cleared in finish()
@@ -239,22 +243,36 @@ public final class PrintManager {
@Override
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- ILayoutResultCallback callback, Bundle metadata) {
+ ILayoutResultCallback callback, Bundle metadata, int sequence) {
+ synchronized (mLock) {
+ if (mLayoutOrWriteCancellation != null) {
+ mLayoutOrWriteCancellation.cancel();
+ }
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = oldAttributes;
args.arg2 = newAttributes;
args.arg3 = callback;
args.arg4 = metadata;
+ args.argi1 = sequence;
+ mHandler.removeMessages(MyHandler.MSG_LAYOUT);
mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
}
@Override
- public void write(List<PageRange> pages, ParcelFileDescriptor fd,
- IWriteResultCallback callback) {
+ public void write(PageRange[] pages, ParcelFileDescriptor fd,
+ IWriteResultCallback callback, int sequence) {
+ synchronized (mLock) {
+ if (mLayoutOrWriteCancellation != null) {
+ mLayoutOrWriteCancellation.cancel();
+ }
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = pages;
args.arg2 = fd.getFileDescriptor();
args.arg3 = callback;
+ args.argi1 = sequence;
+ mHandler.removeMessages(MyHandler.MSG_WRITE);
mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
}
@@ -283,7 +301,6 @@ public final class PrintManager {
}
@Override
- @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
if (isFinished()) {
return;
@@ -295,42 +312,116 @@ public final class PrintManager {
case MSG_LAYOUT: {
SomeArgs args = (SomeArgs) message.obj;
- PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
- PrintAttributes newAttributes = (PrintAttributes) args.arg2;
- ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
- Bundle metadata = (Bundle) args.arg4;
+ final PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
+ final PrintAttributes newAttributes = (PrintAttributes) args.arg2;
+ final ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
+ final Bundle metadata = (Bundle) args.arg4;
+ final int sequence = args.argi1;
args.recycle();
- try {
- ICancellationSignal remoteSignal = CancellationSignal.createTransport();
- callback.onLayoutStarted(remoteSignal);
-
- mDocumentAdapter.onLayout(oldAttributes, newAttributes,
- CancellationSignal.fromTransport(remoteSignal),
- new LayoutResultCallbackWrapper(callback), metadata);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error printing", re);
+ CancellationSignal cancellation = new CancellationSignal();
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = cancellation;
}
+
+ mDocumentAdapter.onLayout(oldAttributes, newAttributes,
+ cancellation, new LayoutResultCallback() {
+ @Override
+ public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+ if (info == null) {
+ throw new IllegalArgumentException("info cannot be null");
+ }
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ try {
+ callback.onLayoutFinished(info, changed, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
+ }
+ }
+
+ @Override
+ public void onLayoutFailed(CharSequence error) {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ try {
+ callback.onLayoutFailed(error, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+ }
+ }
+
+ @Override
+ public void onLayoutCancelled() {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ }
+ }, metadata);
} break;
case MSG_WRITE: {
SomeArgs args = (SomeArgs) message.obj;
- List<PageRange> pages = (List<PageRange>) args.arg1;
- FileDescriptor fd = (FileDescriptor) args.arg2;
- IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+ final PageRange[] pages = (PageRange[]) args.arg1;
+ final FileDescriptor fd = (FileDescriptor) args.arg2;
+ final IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+ final int sequence = args.argi1;
args.recycle();
- try {
- ICancellationSignal remoteSignal = CancellationSignal.createTransport();
- callback.onWriteStarted(remoteSignal);
-
- mDocumentAdapter.onWrite(pages, fd,
- CancellationSignal.fromTransport(remoteSignal),
- new WriteResultCallbackWrapper(callback, fd));
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error printing", re);
- IoUtils.closeQuietly(fd);
+ CancellationSignal cancellation = new CancellationSignal();
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = cancellation;
}
+
+ mDocumentAdapter.onWrite(pages, fd, cancellation,
+ new WriteResultCallback() {
+ @Override
+ public void onWriteFinished(PageRange[] pages) {
+ if (pages == null) {
+ throw new IllegalArgumentException("pages cannot be null");
+ }
+ if (pages.length == 0) {
+ throw new IllegalArgumentException("pages cannot be empty");
+ }
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Close before notifying the other end. We want
+ // to be ready by the time we announce it.
+ IoUtils.closeQuietly(fd);
+ try {
+ callback.onWriteFinished(pages, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onWriteFinished", re);
+ }
+ }
+
+ @Override
+ public void onWriteFailed(CharSequence error) {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Close before notifying the other end. We want
+ // to be ready by the time we announce it.
+ IoUtils.closeQuietly(fd);
+ try {
+ callback.onWriteFailed(error, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+ }
+ }
+
+ @Override
+ public void onWriteCancelled() {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Just close the fd for now.
+ IoUtils.closeQuietly(fd);
+ }
+ });
} break;
case MSG_FINISH: {
@@ -346,67 +437,4 @@ public final class PrintManager {
}
}
}
-
- private static final class WriteResultCallbackWrapper extends WriteResultCallback {
-
- private final IWriteResultCallback mWrappedCallback;
- private final FileDescriptor mFd;
-
- public WriteResultCallbackWrapper(IWriteResultCallback callback,
- FileDescriptor fd) {
- mWrappedCallback = callback;
- mFd = fd;
- }
-
- @Override
- public void onWriteFinished(List<PageRange> pages) {
- try {
- // Close before notifying the other end. We want
- // to be ready by the time we announce it.
- IoUtils.closeQuietly(mFd);
- mWrappedCallback.onWriteFinished(pages);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onWriteFinished", re);
- }
- }
-
- @Override
- public void onWriteFailed(CharSequence error) {
- try {
- // Close before notifying the other end. We want
- // to be ready by the time we announce it.
- IoUtils.closeQuietly(mFd);
- mWrappedCallback.onWriteFailed(error);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onWriteFailed", re);
- }
- }
- }
-
- private static final class LayoutResultCallbackWrapper extends LayoutResultCallback {
-
- private final ILayoutResultCallback mWrappedCallback;
-
- public LayoutResultCallbackWrapper(ILayoutResultCallback callback) {
- mWrappedCallback = callback;
- }
-
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- try {
- mWrappedCallback.onLayoutFinished(info, changed);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
- }
- }
-
- @Override
- public void onLayoutFailed(CharSequence error) {
- try {
- mWrappedCallback.onLayoutFailed(error);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
- }
- }
- }
}
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index da3b6bc..c0daa6e 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -41,8 +41,6 @@ public final class PrinterInfo implements Parcelable {
*/
public static final int DEFAULT_UNDEFINED = -1;
- private static final int MIN_COPIES = 1;
-
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_INPUT_TRAY = 2;
@@ -240,9 +238,6 @@ public final class PrinterInfo implements Parcelable {
public void getDefaults(PrintAttributes outAttributes) {
outAttributes.clear();
- // TODO: Do we want a printer to specify default copies?
- outAttributes.setCopies(MIN_COPIES);
-
outAttributes.setMargins(mDefaultMargins);
final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE);
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 80530a7..0ac5a13 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -61,6 +61,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ if (isInImmutableState()) {
+ return mCachedInfo;
+ }
PrintJobInfo info = null;
try {
info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId());
@@ -182,6 +185,9 @@ public final class PrintJob {
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
+ if (isInImmutableState()) {
+ return false;
+ }
try {
return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag);
} catch (RemoteException re) {
@@ -210,6 +216,12 @@ public final class PrintJob {
return mCachedInfo.getId();
}
+ private boolean isInImmutableState() {
+ final int state = mCachedInfo.getState();
+ return state == PrintJobInfo.STATE_COMPLETED
+ || state == PrintJobInfo.STATE_CANCELED;
+ }
+
private boolean setState(int state) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) {
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity.xml b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
index 32bc15a..a4105ea 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
@@ -34,12 +34,13 @@
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:layout_marginLeft="32dip"
+ android:layout_marginTop="32dip"
android:layout_marginRight="32dip"
android:layout_marginBottom="12dip"
android:layout_row="0"
android:layout_column="0"
android:layout_columnSpan="2"
- android:minHeight="?android:attr/listPreferredItemHeightSmall">
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<!-- Copies -->
@@ -57,7 +58,8 @@
android:layout_gravity="bottom"
android:inputType="numberDecimal"
android:selectAllOnFocus="true"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
@@ -86,7 +88,8 @@
android:layout_marginBottom="12dip"
android:layout_row="2"
android:layout_column="1"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -114,7 +117,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="0"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -142,7 +146,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="1"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -169,7 +174,8 @@
android:layout_marginRight="12dip"
android:layout_row="6"
android:layout_column="0"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<view
@@ -186,10 +192,12 @@
android:minWidth="150dip"
android:hint="@string/pages_range_example"
android:inputType="textNoSuggestions"
- android:visibility="gone">
+ android:visibility="gone"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
+ android:id="@+id/page_range_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="32dip"
@@ -231,7 +239,8 @@
android:layout_columnSpan="2"
android:text="@string/print_preview"
android:gravity="left|center_vertical"
- android:background="?android:attr/selectableItemBackground">
+ android:background="?android:attr/selectableItemBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
<ImageView
@@ -269,7 +278,8 @@
android:layout_columnSpan="2"
android:padding="0dip"
android:text="@string/print_button"
- android:background="?android:attr/selectableItemBackground">
+ android:background="?android:attr/selectableItemBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
</GridLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 27540d7..1762693 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
<string name="app_label">Print Spooler</string>
@@ -38,7 +38,7 @@
<string name="label_orientation">ORIENTATION</string>
<!-- Label of the page selection widget. [CHAR LIMIT=20] -->
- <string name="label_pages">PAGES</string>
+ <string name="label_pages">PAGES (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
<!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=15] -->
<string name="pages_range_example">e.g. 1&#8211;5, 8</string>
@@ -52,6 +52,9 @@
<!-- Title of the message that the printing application crashed. [CHAR LIMIT=50] -->
<string name="printing_app_crashed">Printing app crashed</string>
+ <!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
+ <string name="page_count_unknown">unknown</string>
+
<!-- Color mode labels. -->
<string-array name="color_mode_labels">
<!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 1e1cc24..86c4f37 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -24,9 +24,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -34,24 +32,27 @@ import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
+import android.print.ILayoutResultCallback;
import android.print.IPrintDocumentAdapter;
import android.print.IPrinterDiscoveryObserver;
+import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Choreographer;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -64,10 +65,13 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
-import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -76,15 +80,31 @@ import java.util.regex.Pattern;
*/
public class PrintJobConfigActivity extends Activity {
- private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "PrintJobConfigActivity";
- private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
- public static final String EXTRA_PRINTABLE = "printable";
- public static final String EXTRA_APP_ID = "appId";
- public static final String EXTRA_ATTRIBUTES = "attributes";
+ private static final boolean LIVE_PREVIEW_SUPPORTED = false;
+
+ public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter";
+ public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
public static final String EXTRA_PRINT_JOB_ID = "printJobId";
+ private static final int CONTROLLER_STATE_INITIALIZED = 1;
+ private static final int CONTROLLER_STATE_STARTED = 2;
+ private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
+ private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
+ private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
+ private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
+ private static final int CONTROLLER_STATE_FINISHED = 7;
+ private static final int CONTROLLER_STATE_FAILED = 8;
+ private static final int CONTROLLER_STATE_CANCELLED = 9;
+
+ private static final int EDITOR_STATE_INITIALIZED = 1;
+ private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
+ private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3;
+ private static final int EDITOR_STATE_CANCELLED = 4;
+
private static final int MIN_COPIES = 1;
private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
@@ -95,31 +115,12 @@ public class PrintJobConfigActivity extends Activity {
private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
"([0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*[,]?[\\s]*)+");
- private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
-
- private Handler mHandler;
-
- private Editor mEditor;
-
- private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
-
- private int mAppId;
- private int mPrintJobId;
+ public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().create();
private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().create();
private final PrintAttributes mTempPrintAttributes = new PrintAttributes.Builder().create();
- private RemotePrintDocumentAdapter mRemotePrintAdapter;
-
- private boolean mPrintConfirmed;
-
- private boolean mStarted;
-
- private IBinder mIPrintDocumentAdapter;
-
- private PrintDocumentInfo mPrintDocumentInfo;
-
private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
@@ -127,369 +128,484 @@ public class PrintJobConfigActivity extends Activity {
}
};
- @Override
- protected void onDestroy() {
- mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
- super.onDestroy();
- }
+ private PrintSpooler mSpooler;
+ private Editor mEditor;
+ private Document mDocument;
+ private PrintController mController;
+ private PrinterDiscoveryObserver mPrinterDiscoveryObserver;
+
+ private int mPrintJobId;
+
+ private IBinder mIPrintDocumentAdapter;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.print_job_config_activity);
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
- | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ Bundle extras = getIntent().getExtras();
+
+ mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
+ if (mPrintJobId < 0) {
+ throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
+ }
+
+ mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER);
+ if (mIPrintDocumentAdapter == null) {
+ throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
+ }
+
+ PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_PRINT_ATTRIBUTES);
+ if (attributes != null) {
+ mCurrPrintAttributes.copyFrom(attributes);
+ }
- mHandler = new MyHandler(Looper.getMainLooper());
+ mSpooler = PrintSpooler.getInstance(this);
mEditor = new Editor();
+ mDocument = new Document();
+ mController = new PrintController(new RemotePrintDocumentAdapter(
+ IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
+ mSpooler.generateFileForPrintJob(mPrintJobId)));
}
@Override
protected void onResume() {
super.onResume();
- mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
- notifyPrintableStartIfNeeded();
+ try {
+ mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException re) {
+ finish();
+ return;
+ }
+ mController.initialize();
+ mEditor.initialize();
+ mPrinterDiscoveryObserver = new PrinterDiscoveryObserver(mEditor, getMainLooper());
+ mSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
}
@Override
protected void onPause() {
+ mSpooler.stopPrinterDiscovery();
+ mPrinterDiscoveryObserver.destroy();
+ mPrinterDiscoveryObserver = null;
+ if (mController.isCancelled() || mController.isFailed()) {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ } else if (mController.hasStarted()) {
+ mController.finish();
+ if (mEditor.isPrintConfirmed()) {
+ if (mController.isFinished()) {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_QUEUED);
+ } else {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ }
+ }
+ }
+ mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
super.onPause();
- mPrintSpooler.stopPrinterDiscovery();
- notifyPrintableFinishIfNeeded();
}
- private void notifyPrintableStartIfNeeded() {
- if (mEditor.getCurrentPrinter() == null
- || mStarted) {
- return;
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed()
+ && getWindow().shouldCloseOnTouch(this, event)) {
+ if (!mController.isWorking()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ mEditor.cancel();
+ return true;
}
- mStarted = true;
- mRemotePrintAdapter.start();
+ return super.onTouchEvent(event);
}
- private void updatePrintableContentIfNeeded() {
- if (!mStarted) {
- return;
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ event.startTracking();
}
+ return super.onKeyDown(keyCode, event);
+ }
- mPrintSpooler.setPrintJobAttributes(mPrintJobId, mCurrPrintAttributes);
-
- mRemotePrintAdapter.cancel();
- mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FINISHED);
- mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FAILED);
-
- // TODO: Implement setting the print preview attribute
- mRemotePrintAdapter.layout(mOldPrintAttributes,
- mCurrPrintAttributes, new LayoutResultCallback() {
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
- 0, info).sendToTarget();
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+ && !event.isCanceled()) {
+ if (!mController.isWorking()) {
+ PrintJobConfigActivity.this.finish();
}
+ mEditor.cancel();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
- @Override
- public void onLayoutFailed(CharSequence error) {
- mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
- }
- }, new Bundle());
+ private boolean printAttributesChanged() {
+ return !mOldPrintAttributes.equals(mCurrPrintAttributes);
}
- private void handleOnLayoutFinished(PrintDocumentInfo info, boolean changed) {
- mPrintDocumentInfo = info;
+ private class PrintController {
+ private final AtomicInteger mRequestCounter = new AtomicInteger();
- mEditor.updateUiIfNeeded();
+ private final RemotePrintDocumentAdapter mRemotePrintAdapter;
- // TODO: Handle the case of unchanged content
- mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info);
+ private final Handler mHandler;
- // TODO: Implement page selector.
- final List<PageRange> pages = new ArrayList<PageRange>();
- pages.add(PageRange.ALL_PAGES);
+ private int mControllerState = CONTROLLER_STATE_INITIALIZED;
- mRemotePrintAdapter.write(pages, new WriteResultCallback() {
+ private PageRange[] mRequestedPages;
+
+ private Bundle mMetadata = new Bundle();
+
+ private final ILayoutResultCallback mILayoutResultCallback =
+ new ILayoutResultCallback.Stub() {
@Override
- public void onWriteFinished(List<PageRange> pages) {
- mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
+ public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
+ 0, info).sendToTarget();
+ }
}
@Override
- public void onWriteFailed(CharSequence error) {
- mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
+ public void onLayoutFailed(CharSequence error, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
+ }
}
- });
- }
-
- private void handleOnLayoutFailed(CharSequence error) {
- Log.e(LOG_TAG, "Error during layout: " + error);
- finishActivity(Activity.RESULT_CANCELED);
- }
+ };
- private void handleOnWriteFinished(List<PageRange> pages) {
- // TODO: Now we have to allow the preview button
- mEditor.updatePrintPreview(mRemotePrintAdapter.getFile());
- }
+ private IWriteResultCallback mIWriteResultCallback = new IWriteResultCallback.Stub() {
+ @Override
+ public void onWriteFinished(PageRange[] pages, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
+ }
+ }
- private void handleOnWriteFailed(CharSequence error) {
- Log.e(LOG_TAG, "Error write layout: " + error);
- finishActivity(Activity.RESULT_CANCELED);
- }
+ @Override
+ public void onWriteFailed(CharSequence error, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
+ }
+ }
+ };
- private void notifyPrintableFinishIfNeeded() {
- if (!mStarted) {
- return;
+ public PrintController(RemotePrintDocumentAdapter adapter) {
+ mRemotePrintAdapter = adapter;
+ mHandler = new MyHandler(Looper.getMainLooper());
}
- if (!mPrintConfirmed) {
- mRemotePrintAdapter.cancel();
+ public void initialize() {
+ mControllerState = CONTROLLER_STATE_INITIALIZED;
}
- mRemotePrintAdapter.finish();
- PrinterInfo printer = mEditor.getCurrentPrinter();
- // If canceled or no printer, nothing to do.
- if (!mPrintConfirmed || printer == null) {
- // Update the print job's status.
- mPrintSpooler.setPrintJobState(mPrintJobId,
- PrintJobInfo.STATE_CANCELED);
- return;
+ public void cancel() {
+ mControllerState = CONTROLLER_STATE_CANCELLED;
}
- // Update the print job's printer.
- mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printer.getId());
+ public boolean isCancelled() {
+ return (mControllerState == CONTROLLER_STATE_CANCELLED);
+ }
- // Update the print job's status.
- mPrintSpooler.setPrintJobState(mPrintJobId,
- PrintJobInfo.STATE_QUEUED);
+ public boolean isFinished() {
+ return (mControllerState == CONTROLLER_STATE_FINISHED);
+ }
- if (DEBUG) {
- if (mPrintConfirmed) {
- File file = mRemotePrintAdapter.getFile();
- if (file.exists()) {
- new ViewSpooledFileAsyncTask(file).executeOnExecutor(
- AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
- }
+ public boolean isFailed() {
+ return (mControllerState == CONTROLLER_STATE_FAILED);
}
- }
- private boolean hasPdfViewer() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setType("application/pdf");
- return !getPackageManager().queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
- }
+ public boolean hasStarted() {
+ return mControllerState >= CONTROLLER_STATE_STARTED;
+ }
- // Caution: Use this only for debugging
- private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
+ public boolean hasPerformedLayout() {
+ return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
+ }
- private final File mFile;
+ public boolean isWorking() {
+ return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
+ || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
+ }
- public ViewSpooledFileAsyncTask(File file) {
- mFile = file;
+ public void start() {
+ mControllerState = CONTROLLER_STATE_STARTED;
+ mRemotePrintAdapter.start();
}
- @Override
- protected Void doInBackground(Void... params) {
- mFile.setExecutable(true, false);
- mFile.setWritable(true, false);
- mFile.setReadable(true, false);
+ public void update() {
+ if (!printAttributesChanged()) {
+ // If the attributes changes, then we do not do a layout but may
+ // have to ask the app to write some pages. Hence, pretend layout
+ // completed and nothing changed, so we handle writing as usual.
+ handleOnLayoutFinished(mDocument.info, false);
+ } else {
+ mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes);
- final long identity = Binder.clearCallingIdentity();
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityAsUser(intent, null, UserHandle.CURRENT);
- Binder.restoreCallingIdentity(identity);
- return null;
- }
- }
+ mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
+ !mEditor.isPrintConfirmed());
- private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
- private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
- private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+ mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
- private final Handler mHandler;
+ mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
+ mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
- @SuppressWarnings("unchecked")
- public PrintDiscoveryObserver(Looper looper) {
- mHandler = new Handler(looper, null, true) {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ADD_DICOVERED_PRINTERS: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- mEditor.addPrinters(printers);
- } break;
- case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
- List<PrinterId> printerIds = (List<PrinterId>) message.obj;
- mEditor.removePrinters(printerIds);
- } break;
- }
- }
- };
+ mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
+ }
}
- @Override
- public void addDiscoveredPrinters(List<PrinterInfo> printers) {
- mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
+ public void finish() {
+ mControllerState = CONTROLLER_STATE_FINISHED;
+ mRemotePrintAdapter.finish();
}
- @Override
- public void removeDiscoveredPrinters(List<PrinterId> printers) {
- mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
- }
- }
+ private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) {
+ if (isCancelled()) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- private final class SpinnerItem<T> {
- final T value;
- CharSequence label;
+ mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
- public SpinnerItem(T value, CharSequence label) {
- this.value = value;
- this.label = label;
- }
+ // If the info changed, we update the document and the print job,
+ // and update the UI since the the page range selection may have
+ // become invalid.
+ final boolean infoChanged = !info.equals(mDocument.info);
+ if (infoChanged) {
+ mDocument.info = info;
+ mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info);
+ mEditor.updateUi();
+ }
- public String toString() {
- return label.toString();
- }
- }
+ // If the document info or the layout changed, then
+ // drop the pages since we have to fetch them again.
+ if (infoChanged || layoutChanged) {
+ mDocument.pages = null;
+ }
- /**
- * An instance of this class class is intended to be the first focusable
- * in a layout to which the system automatically gives focus. It performs
- * some voodoo to avoid the first tap on it to start an edit mode, rather
- * to bring up the IME, i.e. to get the behavior as if the view was not
- * focused.
- */
- public static final class CustomEditText extends EditText {
- private boolean mClickedBeforeFocus;
+ // No pages means that the user selected an invalid range while we
+ // were doing a layout or the layout returned a document info for
+ // which the selected range is invalid. In such a case we do not
+ // write anything and wait for the user to fix the range which will
+ // trigger an update.
+ mRequestedPages = mEditor.getRequestedPages();
+ if (mRequestedPages == null) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- public CustomEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
+ // If the info and the layout did not change and we already have
+ // the requested pages, then nothing else to do.
+ if (!infoChanged && !layoutChanged
+ && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- @Override
- public boolean performClick() {
- super.performClick();
- if (isFocused() && !mClickedBeforeFocus) {
- clearFocus();
- requestFocus();
+ // If we do not support live preview and the current layout is
+ // not for preview purposes, i.e. the user did not poke the
+ // preview button, then just skip the write.
+ if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed()
+ && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
}
- mClickedBeforeFocus = true;
- return true;
+
+ // Request a write of the pages of interest.
+ mControllerState = CONTROLLER_STATE_WRITE_STARTED;
+ mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback,
+ mRequestCounter.incrementAndGet());
}
- @Override
- public void setError(CharSequence error, Drawable icon) {
- setCompoundDrawables(null, null, icon, null);
+ private void handleOnLayoutFailed(CharSequence error) {
+ mControllerState = CONTROLLER_STATE_FAILED;
+ // TODO: We need some UI for announcing an error.
+ Log.e(LOG_TAG, "Error during layout: " + error);
+ PrintJobConfigActivity.this.finish();
}
- protected void onFocusChanged(boolean gainFocus, int direction,
- Rect previouslyFocusedRect) {
- if (!gainFocus) {
- mClickedBeforeFocus = false;
+ private void handleOnWriteFinished(PageRange[] pages) {
+ if (isCancelled()) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
}
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- }
- }
- private final class MyHandler extends Handler {
- public static final int MSG_ON_LAYOUT_FINISHED = 1;
- public static final int MSG_ON_LAYOUT_FAILED = 2;
- public static final int MSG_ON_WRITE_FINISHED = 3;
- public static final int MSG_ON_WRITE_FAILED = 4;
+ mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
+
+ // Update which pages we have fetched.
+ mDocument.pages = PageRangeUtils.normalize(pages);
- public MyHandler(Looper looper) {
- super(looper, null, false);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
+ + " and got: " + Arrays.toString(mDocument.pages));
+ }
+
+ // Adjust the print job pages based on what was requested and written.
+ // The cases are ordered in the most expected to the least expected.
+ if (Arrays.equals(mDocument.pages, mRequestedPages)) {
+ // We got a document with exactly the pages we wanted. Hence,
+ // the printer has to print all pages in the data.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY);
+ } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
+ // We requested specific pages but got all of them. Hence,
+ // the printer has to print only the requested pages.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages);
+ } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+ // We requested specific pages and got more but not all pages.
+ // Hence, we have to offset appropriately the printed pages to
+ // exclude the pages we did not request. Note that pages is
+ // guaranteed to be not null and not empty.
+ final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
+ PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
+ PageRangeUtils.offsetStart(offsetPages, offset);
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages);
+ } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
+ && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
+ && mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) {
+ // We requested all pages via the special constant and got all
+ // of them as an explicit enumeration. Hence, the printer has
+ // to print only the requested pages.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages);
+ } else {
+ // We did not get the pages we requested, then the application
+ // misbehaves, so we fail quickly.
+ // TODO: We need some UI for announcing an error.
+ mControllerState = CONTROLLER_STATE_FAILED;
+ Log.e(LOG_TAG, "Received invalid pages from the app");
+ PrintJobConfigActivity.this.finish();
+ }
+
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
}
- @Override
- @SuppressWarnings("unchecked")
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_ON_LAYOUT_FINISHED: {
- PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
- final boolean changed = (message.arg1 == 1);
- handleOnLayoutFinished(info, changed);
- } break;
-
- case MSG_ON_LAYOUT_FAILED: {
- CharSequence error = (CharSequence) message.obj;
- handleOnLayoutFailed(error);
- } break;
-
- case MSG_ON_WRITE_FINISHED: {
- List<PageRange> pages = (List<PageRange>) message.obj;
- handleOnWriteFinished(pages);
- } break;
-
- case MSG_ON_WRITE_FAILED: {
- CharSequence error = (CharSequence) message.obj;
- handleOnWriteFailed(error);
- } break;
+ private void handleOnWriteFailed(CharSequence error) {
+ mControllerState = CONTROLLER_STATE_FAILED;
+ Log.e(LOG_TAG, "Error during write: " + error);
+ PrintJobConfigActivity.this.finish();
+ }
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_ON_LAYOUT_FINISHED = 1;
+ public static final int MSG_ON_LAYOUT_FAILED = 2;
+ public static final int MSG_ON_WRITE_FINISHED = 3;
+ public static final int MSG_ON_WRITE_FAILED = 4;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ON_LAYOUT_FINISHED: {
+ PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
+ final boolean changed = (message.arg1 == 1);
+ mController.handleOnLayoutFinished(info, changed);
+ } break;
+
+ case MSG_ON_LAYOUT_FAILED: {
+ CharSequence error = (CharSequence) message.obj;
+ mController.handleOnLayoutFailed(error);
+ } break;
+
+ case MSG_ON_WRITE_FINISHED: {
+ PageRange[] pages = (PageRange[]) message.obj;
+ mController.handleOnWriteFinished(pages);
+ } break;
+
+ case MSG_ON_WRITE_FAILED: {
+ CharSequence error = (CharSequence) message.obj;
+ mController.handleOnWriteFailed(error);
+ } break;
+ }
}
}
}
- private class Editor {
- private EditText mCopiesEditText;
+ private final class Editor {
+ private final EditText mCopiesEditText;
+
+ private final TextView mRangeTitle;
+ private final EditText mRangeEditText;
- private EditText mRangeEditText;
+ private final Spinner mDestinationSpinner;
+ private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
- private Spinner mDestinationSpinner;
- public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+ private final Spinner mMediaSizeSpinner;
+ private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
- private Spinner mMediaSizeSpinner;
- public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+ private final Spinner mColorModeSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
- private Spinner mColorModeSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+ private final Spinner mOrientationSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
- private Spinner mOrientationSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+ private final Spinner mRangeOptionsSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
- private Spinner mRangeOptionsSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
+ private final SimpleStringSplitter mStringCommaSplitter =
+ new SimpleStringSplitter(',');
- private Button mPrintPreviewButton;
+ private final Button mPrintPreviewButton;
- private Button mPrintButton;
+ private final Button mPrintButton;
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
if (spinner == mDestinationSpinner) {
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.clear();
- final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
- if (selectedIndex >= 0) {
- mDestinationSpinnerAdapter.getItem(selectedIndex).value.getDefaults(
- mCurrPrintAttributes);
+ SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
+ if (dstItem != null) {
+ mSpooler.setPrintJobPrinterIdNoPersistence(mPrintJobId, dstItem.value.getId());
+ dstItem.value.getDefaults(mCurrPrintAttributes);
+ }
+ updateUi();
+ if (!mController.hasStarted()) {
+ mController.start();
+ }
+ if (!hasErrors()) {
+ mController.update();
}
- updateUiIfNeeded();
- notifyPrintableStartIfNeeded();
- updatePrintableContentIfNeeded();
} else if (spinner == mMediaSizeSpinner) {
SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setMediaSize(mediaItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mColorModeSpinner) {
SpinnerItem<Integer> colorModeItem =
mColorModeSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setColorMode(colorModeItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mOrientationSpinner) {
SpinnerItem<Integer> orientationItem =
mOrientationSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setOrientation(orientationItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mRangeOptionsSpinner) {
- updateUiIfNeeded();
- updatePrintableContentIfNeeded();
+ updateUi();
+ if (!hasErrors()) {
+ mController.update();
+ }
}
}
@@ -512,19 +628,28 @@ public class PrintJobConfigActivity extends Activity {
@Override
public void afterTextChanged(Editable editable) {
+ final boolean hadErrors = hasErrors();
+
if (editable.length() == 0) {
mCopiesEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
+
final int copies = Integer.parseInt(editable.toString());
if (copies < MIN_COPIES) {
mCopiesEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
- mCurrPrintAttributes.setCopies(copies);
+
+ mCopiesEditText.setError(null);
+ mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+ updateUi();
+
+ if (hadErrors && !hasErrors() && printAttributesChanged()) {
+ mController.update();
+ }
}
};
@@ -541,18 +666,20 @@ public class PrintJobConfigActivity extends Activity {
@Override
public void afterTextChanged(Editable editable) {
+ final boolean hadErrors = hasErrors();
+
String text = editable.toString();
if (TextUtils.isEmpty(text)) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
@@ -560,100 +687,36 @@ public class PrintJobConfigActivity extends Activity {
while (matcher.find()) {
String numericString = text.substring(matcher.start(), matcher.end());
final int pageIndex = Integer.parseInt(numericString);
- if (pageIndex < 1 || pageIndex > mPrintDocumentInfo.getPageCount()) {
+ if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
}
+ //TODO: Catch the error if start is less grater than the end.
+
mRangeEditText.setError(null);
mPrintButton.setEnabled(true);
- }
- };
-
- public Editor() {
- Bundle extras = getIntent().getExtras();
-
- mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
- if (mPrintJobId < 0) {
- throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
- }
-
- mAppId = extras.getInt(EXTRA_APP_ID, -1);
- if (mAppId < 0) {
- throw new IllegalArgumentException("Invalid app id: " + mAppId);
- }
-
- PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
- if (attributes == null) {
- mCurrPrintAttributes.copyFrom(attributes);
- }
+ updateUi();
- mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE);
- if (mIPrintDocumentAdapter == null) {
- throw new IllegalArgumentException("Printable cannot be null");
- }
- mRemotePrintAdapter = new RemotePrintDocumentAdapter(
- IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
- mPrintSpooler.generateFileForPrintJob(mPrintJobId));
-
- try {
- mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException re) {
- finish();
+ if (hadErrors && !hasErrors() && printAttributesChanged()) {
+ updateUi();
+ }
}
+ };
- mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
-
- bindUi();
- }
+ private int mEditorState;
- private void bindUi() {
+ public Editor() {
// Copies
mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
- mCopiesEditText.setText(String.valueOf(MIN_COPIES));
mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
- mCopiesEditText.setText(String.valueOf(
- Math.max(mCurrPrintAttributes.getCopies(), MIN_COPIES)));
mCopiesEditText.selectAll();
// Destination.
mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
- mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(
- PrintJobConfigActivity.this, R.layout.spinner_dropdown_item) {
- @Override
- public View getDropDownView(int position, View convertView,
- ViewGroup parent) {
- return getView(position, convertView, parent);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = getLayoutInflater().inflate(
- R.layout.spinner_dropdown_item, parent, false);
- }
-
- PrinterInfo printerInfo = getItem(position).value;
- TextView title = (TextView) convertView.findViewById(R.id.title);
- title.setText(printerInfo.getLabel());
-
- try {
- TextView subtitle = (TextView)
- convertView.findViewById(R.id.subtitle);
- PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(
- printerInfo.getId().getService().getPackageName(), 0);
- subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
- subtitle.setVisibility(View.VISIBLE);
- } catch (NameNotFoundException nnfe) {
- /* ignore */
- }
-
- return convertView;
- }
- };
+ mDestinationSpinnerAdapter = new DestinationAdapter();
mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
@@ -682,6 +745,7 @@ public class PrintJobConfigActivity extends Activity {
mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Range
+ mRangeTitle = (TextView) findViewById(R.id.page_range_title);
mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
mRangeEditText.addTextChangedListener(mRangeTextWatcher);
@@ -690,8 +754,6 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
- mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
final int[] rangeOptionsValues = getResources().getIntArray(
R.array.page_options_values);
String[] rangeOptionsLabels = getResources().getStringArray(
@@ -701,13 +763,29 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
rangeOptionsValues[i], rangeOptionsLabels[i]));
}
+ mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
mRangeOptionsSpinner.setSelection(0);
+ // Here is some voodoo to circumvent the weird behavior of AdapterView
+ // in which a selection listener may get a callback for an event that
+ // happened before the listener was registered. The reason for that is
+ // that the selection change is handled on the next layout pass.
+ Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
+ new Runnable() {
+ @Override
+ public void run() {
+ mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ }
+ }, null, Choreographer.getFrameDelay() * 2);
mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button);
mPrintPreviewButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- // TODO: Implement
+ mEditor.confirmPreview();
+ // TODO: Implement me
+ Toast.makeText(PrintJobConfigActivity.this,
+ "Stop poking me! Not implemented yet :)",
+ Toast.LENGTH_LONG).show();
}
});
@@ -715,21 +793,106 @@ public class PrintJobConfigActivity extends Activity {
mPrintButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mPrintConfirmed = true;
- finish();
+ mEditor.confirmPrint();
+ updateUi();
+ mController.update();
}
});
}
- private void updateUiIfNeeded() {
+ public void initialize() {
+ mEditorState = EDITOR_STATE_INITIALIZED;
+ mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+ }
+
+ public boolean isCancelled() {
+ return mEditorState == EDITOR_STATE_CANCELLED;
+ }
+
+ public void cancel() {
+ mEditorState = EDITOR_STATE_CANCELLED;
+ mController.cancel();
+ updateUi();
+ }
+
+ public boolean isDone() {
+ return isPrintConfirmed() || isPreviewConfirmed() || isCancelled();
+ }
+
+ public boolean isPrintConfirmed() {
+ return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public void confirmPrint() {
+ mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public boolean isPreviewConfirmed() {
+ return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public void confirmPreview() {
+ mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW;
+ }
+
+ public PageRange[] getRequestedPages() {
+ if (hasErrors()) {
+ return null;
+ }
+ if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+ List<PageRange> pageRanges = new ArrayList<PageRange>();
+ mStringCommaSplitter.setString(mRangeEditText.getText().toString());
+
+ while (mStringCommaSplitter.hasNext()) {
+ String range = mStringCommaSplitter.next().trim();
+ final int dashIndex = range.indexOf('-');
+ final int fromIndex;
+ final int toIndex;
+
+ if (dashIndex > 0) {
+ fromIndex = Integer.parseInt(range.substring(0, dashIndex)) - 1;
+ toIndex = Integer.parseInt(range.substring(
+ dashIndex + 1, range.length())) - 1;
+ } else {
+ fromIndex = toIndex = Integer.parseInt(range);
+ }
+
+ PageRange pageRange = new PageRange(fromIndex, toIndex);
+ pageRanges.add(pageRange);
+ }
+
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+
+ return PageRangeUtils.normalize(pageRangesArray);
+ }
+
+ return ALL_PAGES_ARRAY;
+ }
+
+ public void updateUi() {
+ if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
+ mDestinationSpinner.setEnabled(false);
+ mCopiesEditText.setEnabled(false);
+ mMediaSizeSpinner.setEnabled(false);
+ mColorModeSpinner.setEnabled(false);
+ mOrientationSpinner.setEnabled(false);
+ mRangeOptionsSpinner.setEnabled(false);
+ mRangeEditText.setEnabled(false);
+ mPrintPreviewButton.setEnabled(false);
+ mPrintButton.setEnabled(false);
+ return;
+ }
+
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
if (selectedIndex < 0) {
// Destination
mDestinationSpinner.setEnabled(false);
- // Copies
- mCopiesEditText.setText("1");
+ mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher);
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
mCopiesEditText.setEnabled(false);
// Media size
@@ -751,7 +914,11 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinner.setOnItemSelectedListener(null);
mRangeOptionsSpinner.setSelection(0);
mRangeOptionsSpinner.setEnabled(false);
+ mRangeTitle.setText(getString(R.string.label_pages,
+ getString(R.string.page_count_unknown)));
+ mRangeEditText.removeTextChangedListener(mRangeTextWatcher);
mRangeEditText.setText("");
+ mRangeEditText.addTextChangedListener(mRangeTextWatcher);
mRangeEditText.setEnabled(false);
mRangeEditText.setVisibility(View.INVISIBLE);
@@ -884,46 +1051,65 @@ public class PrintJobConfigActivity extends Activity {
}
// Range options
- if (mPrintDocumentInfo != null && (mPrintDocumentInfo.getPageCount() > 1
- || mPrintDocumentInfo.getPageCount()
- == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
+ PrintDocumentInfo info = mDocument.info;
+ if (info != null && (info.getPageCount() > 1
+ || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
mRangeOptionsSpinner.setEnabled(true);
if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
&& !mRangeEditText.isEnabled()) {
mRangeEditText.setEnabled(true);
- mRangeEditText.setError("");
mRangeEditText.setVisibility(View.VISIBLE);
mRangeEditText.requestFocus();
InputMethodManager imm = (InputMethodManager)
getSystemService(INPUT_METHOD_SERVICE);
imm.showSoftInput(mRangeEditText, 0);
}
+ final int pageCount = mDocument.info.getPageCount();
+ mRangeTitle.setText(getString(R.string.label_pages,
+ (pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+ ? getString(R.string.page_count_unknown)
+ : String.valueOf(pageCount)));
} else {
mRangeOptionsSpinner.setOnItemSelectedListener(null);
mRangeOptionsSpinner.setSelection(0);
mRangeOptionsSpinner.setEnabled(false);
+ mRangeTitle.setText(getString(R.string.label_pages,
+ getString(R.string.page_count_unknown)));
mRangeEditText.setEnabled(false);
- mRangeEditText.setText("");
mRangeEditText.setVisibility(View.INVISIBLE);
}
- // Print preview
- mPrintPreviewButton.setEnabled(true);
- if (hasPdfViewer()) {
- mPrintPreviewButton.setText(getString(R.string.print_preview));
+ // Print/Print preview
+ if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
+ && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors()))
+ || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+ && (!mController.hasPerformedLayout() || hasErrors()))) {
+ mPrintPreviewButton.setEnabled(false);
+ mPrintButton.setEnabled(false);
} else {
- mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+ mPrintPreviewButton.setEnabled(true);
+ if (hasPdfViewer()) {
+ mPrintPreviewButton.setText(getString(R.string.print_preview));
+ } else {
+ mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+ }
+ mPrintButton.setEnabled(true);
}
- // Print
- mPrintButton.setEnabled(true);
+ // Copies
+ if (mCopiesEditText.getError() == null
+ && TextUtils.isEmpty(mCopiesEditText.getText())) {
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.selectAll();
+ mCopiesEditText.requestFocus();
+ }
}
// Here is some voodoo to circumvent the weird behavior of AdapterView
// in which a selection listener may get a callback for an event that
// happened before the listener was registered. The reason for that is
// that the selection change is handled on the next layout pass.
- Choreographer.getInstance().postCallback(Choreographer.CALLBACK_TRAVERSAL,
+ Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
new Runnable() {
@Override
public void run() {
@@ -932,15 +1118,7 @@ public class PrintJobConfigActivity extends Activity {
mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
}
- }, null);
- }
-
- public PrinterInfo getCurrentPrinter() {
- final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
- if (selectedIndex >= 0) {
- return mDestinationSpinnerAdapter.getItem(selectedIndex).value;
- }
- return null;
+ }, null, Choreographer.getFrameDelay() * 2);
}
public void addPrinters(List<PrinterInfo> addedPrinters) {
@@ -995,8 +1173,261 @@ public class PrintJobConfigActivity extends Activity {
}
}
- private void updatePrintPreview(File file) {
- // TODO: Implement
+ private boolean hasErrors() {
+ return mRangeEditText.getError() != null
+ || mCopiesEditText.getError() != null;
+ }
+
+ private boolean hasPdfViewer() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setType("application/pdf");
+ return !getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
+ }
+
+ private final class SpinnerItem<T> {
+ final T value;
+ CharSequence label;
+
+ public SpinnerItem(T value, CharSequence label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ public String toString() {
+ return label.toString();
+ }
+ }
+
+ private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> {
+
+ public DestinationAdapter() {
+ super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item);
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView,
+ ViewGroup parent) {
+ return getView(position, convertView, parent);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(
+ R.layout.spinner_dropdown_item, parent, false);
+ }
+
+ PrinterInfo printerInfo = getItem(position).value;
+ TextView title = (TextView) convertView.findViewById(R.id.title);
+ title.setText(printerInfo.getLabel());
+
+ try {
+ TextView subtitle = (TextView)
+ convertView.findViewById(R.id.subtitle);
+ PackageManager pm = getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(
+ printerInfo.getId().getService().getPackageName(), 0);
+ subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
+ subtitle.setVisibility(View.VISIBLE);
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
+
+ return convertView;
+ }
+ }
+ }
+
+ private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
+ private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
+ private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+
+ private Handler mHandler;
+ private Editor mEditor;
+
+ @SuppressWarnings("unchecked")
+ public PrinterDiscoveryObserver(Editor editor, Looper looper) {
+ mEditor = editor;
+ mHandler = new Handler(looper, null, true) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ADD_DICOVERED_PRINTERS: {
+ List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+ mEditor.addPrinters(printers);
+ } break;
+ case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ mEditor.removePrinters(printerIds);
+ } break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void addDiscoveredPrinters(List<PrinterInfo> printers) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers)
+ .sendToTarget();
+ }
+ }
+ }
+
+ @Override
+ public void removeDiscoveredPrinters(List<PrinterId> printers) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers)
+ .sendToTarget();
+ }
+ }
+ }
+
+ public void destroy() {
+ synchronized (this) {
+ mHandler = null;
+ mEditor = null;
+ }
+ }
+ }
+
+ /**
+ * An instance of this class class is intended to be the first focusable
+ * in a layout to which the system automatically gives focus. It performs
+ * some voodoo to avoid the first tap on it to start an edit mode, rather
+ * to bring up the IME, i.e. to get the behavior as if the view was not
+ * focused.
+ */
+ public static final class CustomEditText extends EditText {
+ private boolean mClickedBeforeFocus;
+ private CharSequence mError;
+
+ public CustomEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean performClick() {
+ super.performClick();
+ if (isFocused() && !mClickedBeforeFocus) {
+ clearFocus();
+ requestFocus();
+ }
+ mClickedBeforeFocus = true;
+ return true;
+ }
+
+ @Override
+ public CharSequence getError() {
+ return mError;
+ }
+
+ @Override
+ public void setError(CharSequence error, Drawable icon) {
+ setCompoundDrawables(null, null, icon, null);
+ mError = error;
+ }
+
+ protected void onFocusChanged(boolean gainFocus, int direction,
+ Rect previouslyFocusedRect) {
+ if (!gainFocus) {
+ mClickedBeforeFocus = false;
+ }
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ }
+ }
+
+ private static final class Document {
+ public PrintDocumentInfo info;
+ public PageRange[] pages;
+ }
+
+ private static final class PageRangeUtils {
+
+ private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
+ @Override
+ public int compare(PageRange lhs, PageRange rhs) {
+ return lhs.getStart() - rhs.getStart();
+ }
+ };
+
+ private PageRangeUtils() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static boolean contains(PageRange[] ourPageRanges, PageRange[] otherPageRanges) {
+ if (ourPageRanges == null || otherPageRanges == null) {
+ return false;
+ }
+
+ otherPageRanges = normalize(otherPageRanges);
+
+ int otherPageIdx = 0;
+ final int myPageCount = ourPageRanges.length;
+ final int otherPageCount = otherPageRanges.length;
+ for (int i= 0; i < myPageCount; i++) {
+ PageRange myPage = ourPageRanges[i];
+ for (; otherPageIdx < otherPageCount; otherPageIdx++) {
+ PageRange otherPage = otherPageRanges[otherPageIdx];
+ if (otherPage.getStart() > myPage.getStart()) {
+ break;
+ }
+ if ((otherPage.getStart() < myPage.getStart()
+ && otherPage.getEnd() > myPage.getStart())
+ || (otherPage.getEnd() > myPage.getEnd()
+ && otherPage.getStart() < myPage.getEnd())
+ || (otherPage.getEnd() < myPage.getStart())) {
+ return false;
+ }
+ }
+ }
+ if (otherPageIdx < otherPageCount) {
+ return false;
+ }
+ return true;
+ }
+
+ public static PageRange[] normalize(PageRange[] pageRanges) {
+ if (pageRanges == null) {
+ return null;
+ }
+ final int oldPageCount = pageRanges.length;
+ if (oldPageCount <= 1) {
+ return pageRanges;
+ }
+ Arrays.sort(pageRanges, sComparator);
+ int newRangeCount = 0;
+ for (int i = 0; i < oldPageCount - 1; i++) {
+ newRangeCount++;
+ PageRange currentRange = pageRanges[i];
+ PageRange nextRange = pageRanges[i + 1];
+ if (currentRange.getEnd() >= nextRange.getStart()) {
+ newRangeCount--;
+ pageRanges[i] = null;
+ pageRanges[i + 1] = new PageRange(currentRange.getStart(),
+ nextRange.getEnd());
+ }
+ }
+ if (newRangeCount == oldPageCount) {
+ return pageRanges;
+ }
+ return Arrays.copyOfRange(pageRanges, oldPageCount - newRangeCount,
+ oldPageCount - 1);
+ }
+
+ public static void offsetStart(PageRange[] pageRanges, int offset) {
+ if (offset == 0) {
+ return;
+ }
+ final int pageRangeCount = pageRanges.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ final int start = pageRanges[i].getStart() + offset;
+ final int end = pageRanges[i].getEnd() + offset;
+ pageRanges[i] = new PageRange(start, end);
+ }
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
index 53ae145..cef4341 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -19,6 +19,9 @@ package com.android.printspooler;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintClient;
@@ -39,6 +42,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import libcore.io.IoUtils;
@@ -59,9 +63,9 @@ import java.util.Map;
public class PrintSpooler {
- private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
+ private static final String LOG_TAG = "PrintSpooler";
- private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
private static final boolean DEBUG_PERSISTENCE = true;
@@ -81,6 +85,8 @@ public class PrintSpooler {
private final PersistenceManager mPersistanceManager;
+ private final Handler mHandler;
+
private final Context mContext;
public IPrintSpoolerClient mClient;
@@ -97,6 +103,7 @@ public class PrintSpooler {
private PrintSpooler(Context context) {
mContext = context;
mPersistanceManager = new PersistenceManager(context);
+ mHandler = new MyHandler(context.getMainLooper());
}
public void setCleint(IPrintSpoolerClient client) {
@@ -112,36 +119,25 @@ public class PrintSpooler {
}
public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
- IPrintSpoolerClient client = null;
synchronized (mLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- client.onStartPrinterDiscovery(observer);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
- }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = observer;
+ mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
+ args).sendToTarget();
}
}
public void stopPrinterDiscovery() {
- IPrintSpoolerClient client = null;
synchronized (mLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- client.onStopPrinterDiscovery();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
- }
+ mHandler.obtainMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY,
+ mClient).sendToTarget();
}
}
public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, int appId) {
+ List<PrintJobInfo> foundPrintJobs = null;
synchronized (mLock) {
- List<PrintJobInfo> foundPrintJobs = null;
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
@@ -162,8 +158,8 @@ public class PrintSpooler {
foundPrintJobs.add(printJob);
}
}
- return foundPrintJobs;
}
+ return foundPrintJobs;
}
public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
@@ -172,11 +168,12 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
if (printJob.getId() == printJobId
- && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
+ && (appId == PrintManager.APP_ID_ANY
+ || appId == printJob.getAppId())) {
return printJob;
}
}
- return null;
+ return null;
}
}
@@ -217,7 +214,7 @@ public class PrintSpooler {
Map<ComponentName, List<PrintJobInfo>> activeJobsPerServiceMap =
new HashMap<ComponentName, List<PrintJobInfo>>();
- synchronized(mLock) {
+ synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
@@ -265,16 +262,25 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobs.get(i);
if (printJob.getState() == PrintJobInfo.STATE_QUEUED) {
- callOnPrintJobQueuedQuietly(client, printJob);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = new PrintJobInfo(printJob);
+ mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
+ args).sendToTarget();
}
}
} else {
- callOnAllPrintJobsForServiceHandledQuietly(client, service);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = service;
+ mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+ args).sendToTarget();
}
}
if (allPrintJobsHandled) {
- callOnAllPrintJobsHandledQuietly(client);
+ mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
+ client).sendToTarget();
}
}
@@ -297,37 +303,43 @@ public class PrintSpooler {
return false;
}
- @SuppressWarnings("resource")
- public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+ final PrintJobInfo printJob;
synchronized (mLock) {
- FileInputStream in = null;
- FileOutputStream out = null;
- try {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- File file = generateFileForPrintJob(printJobId);
- in = new FileInputStream(file);
- out = new FileOutputStream(fd.getFileDescriptor());
+ printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ if (printJob != null) {
+ File file = generateFileForPrintJob(printJobId);
+ in = new FileInputStream(file);
+ out = new FileOutputStream(fd.getFileDescriptor());
+ }
final byte[] buffer = new byte[8192];
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
- return true;
+ return null;
}
out.write(buffer, 0, readByteCount);
}
+ } catch (FileNotFoundException fnfe) {
+ Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error writing print job data!", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(fd);
}
- } catch (FileNotFoundException fnfe) {
- Log.e(LOG_TAG, "Error writing print job data!", fnfe);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error writing print job data!", ioe);
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(fd);
+ Log.i(LOG_TAG, "[END WRITE]");
+ return null;
}
- }
- return false;
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
public File generateFileForPrintJob(int printJobId) {
@@ -354,28 +366,24 @@ public class PrintSpooler {
public boolean setPrintJobState(int printJobId, int state) {
boolean success = false;
- boolean allPrintJobsHandled = false;
- boolean allPrintJobsForServiceHandled = false;
-
- IPrintSpoolerClient client = null;
- PrintJobInfo queuedPrintJob = null;
- PrintJobInfo removedPrintJob = null;
-
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
- client = mClient;
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null && printJob.getState() < state) {
success = true;
printJob.setState(state);
+
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+ }
+
// TODO: Update notifications.
switch (state) {
case PrintJobInfo.STATE_COMPLETED:
case PrintJobInfo.STATE_CANCELED: {
- removedPrintJob = printJob;
removePrintJobLocked(printJob);
// No printer means creation of a print job was cancelled,
@@ -387,83 +395,46 @@ public class PrintSpooler {
return true;
}
- allPrintJobsHandled = !hasActivePrintJobsLocked();
- allPrintJobsForServiceHandled = !hasActivePrintJobsForServiceLocked(
- printerId.getService());
+ ComponentName service = printerId.getService();
+ if (!hasActivePrintJobsForServiceLocked(service)) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = service;
+ mHandler.obtainMessage(
+ MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+ args).sendToTarget();
+ }
+
+ if (!hasActivePrintJobsLocked()) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED,
+ mClient).sendToTarget();
+ }
} break;
case PrintJobInfo.STATE_QUEUED: {
- queuedPrintJob = new PrintJobInfo(printJob);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = new PrintJobInfo(printJob);
+ mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
+ args).sendToTarget();
} break;
}
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
+
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
}
- mPersistanceManager.writeStateLocked();
}
}
- if (queuedPrintJob != null) {
- callOnPrintJobQueuedQuietly(client, queuedPrintJob);
- }
-
- if (allPrintJobsForServiceHandled) {
- callOnAllPrintJobsForServiceHandledQuietly(client,
- removedPrintJob.getPrinterId().getService());
- }
-
- if (allPrintJobsHandled) {
- callOnAllPrintJobsHandledQuietly(client);
- }
-
return success;
}
- private void callOnPrintJobQueuedQuietly(IPrintSpoolerClient client,
- PrintJobInfo printJob) {
- try {
- client.onPrintJobQueued(printJob);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
- }
- }
-
- private void callOnAllPrintJobsForServiceHandledQuietly(IPrintSpoolerClient client,
- ComponentName service) {
- try {
- client.onAllPrintJobsForServiceHandled(service);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
- }
- }
-
- private void callOnAllPrintJobsHandledQuietly(final IPrintSpoolerClient client) {
- // This has to run on the tread that is persisting the current state
- // since this call may result in the system unbinding from the spooler
- // and as a result the spooler process may get killed before the write
- // completes.
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- client.onAllPrintJobsHandled();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
- }
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
private boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- return true;
- }
+ if (!isActiveState(printJob.getState())) {
+ return true;
}
}
return false;
@@ -473,72 +444,89 @@ public class PrintSpooler {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- if (printJob.getPrinterId().getService().equals(service)) {
- return true;
- }
- } break;
+ if (!isActiveState(printJob.getState())
+ && printJob.getPrinterId().getService().equals(service)) {
+ return true;
}
}
return false;
}
+ private static boolean isActiveState(int printJobState) {
+ return printJobState != PrintJobInfo.STATE_CREATED
+ || printJobState != PrintJobInfo.STATE_QUEUED
+ || printJobState != PrintJobInfo.STATE_STARTED;
+ }
+
public boolean setPrintJobTag(int printJobId, String tag) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
+ String printJobTag = printJob.getTag();
+ if (printJobTag == null) {
+ if (tag == null) {
+ return false;
+ }
+ } else if (printJobTag.equals(tag)) {
+ return false;
+ }
printJob.setTag(tag);
- mPersistanceManager.writeStateLocked();
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
return true;
}
}
return false;
}
- public final boolean setPrintJobPrintDocumentInfo(int printJobId, PrintDocumentInfo info) {
+ public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setCopies(copies);
+ }
+ }
+ }
+
+ public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setDocumentInfo(info);
- mPersistanceManager.writeStateLocked();
- return true;
}
}
- return false;
}
- public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
+ public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setAttributes(attributes);
- mPersistanceManager.writeStateLocked();
}
}
}
- public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
+ public void setPrintJobPrinterIdNoPersistence(int printJobId, PrinterId printerId) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPrinterId(printerId);
- mPersistanceManager.writeStateLocked();
}
}
}
- public boolean setPrintJobPages(int printJobId, PageRange[] pages) {
+ public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPages(pages);
- mPersistanceManager.writeStateLocked();
- return true;
}
}
- return false;
+ }
+
+ private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+ return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
}
private final class PersistenceManager {
@@ -558,6 +546,7 @@ public class PrintSpooler {
private static final String ATTR_APP_ID = "appId";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_TAG = "tag";
+ private static final String ATTR_COPIES = "copies";
private static final String TAG_MEDIA_SIZE = "mediaSize";
private static final String TAG_RESOLUTION = "resolution";
@@ -620,6 +609,9 @@ public class PrintSpooler {
}
private void doWriteStateLocked() {
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST START]");
+ }
FileOutputStream out = null;
try {
out = mStatePersistFile.startWrite();
@@ -652,6 +644,7 @@ public class PrintSpooler {
if (tag != null) {
serializer.attribute(null, ATTR_TAG, tag);
}
+ serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
@@ -775,6 +768,9 @@ public class PrintSpooler {
serializer.endTag(null, TAG_SPOOLER);
serializer.endDocument();
mStatePersistFile.finishWrite(out);
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
} catch (IOException e) {
Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
mStatePersistFile.failWrite(out);
@@ -861,6 +857,8 @@ public class PrintSpooler {
printJob.setUserId(userId);
String tag = parser.getAttributeValue(null, ATTR_TAG);
printJob.setTag(tag);
+ String copies = parser.getAttributeValue(null, ATTR_TAG);
+ printJob.setCopies(Integer.parseInt(copies));
parser.next();
@@ -892,7 +890,9 @@ public class PrintSpooler {
parser.next();
}
if (pageRanges != null) {
- printJob.setPages((PageRange[]) pageRanges.toArray());
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+ printJob.setPages(pageRangesArray);
}
skipEmptyTextTags(parser);
@@ -1054,4 +1054,93 @@ public class PrintSpooler {
return true;
}
}
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
+ public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
+ public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
+ public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 5;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ON_START_PRINTER_DISCOVERY: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onStartPrinterDiscovery(observer);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_ON_STOP_PRINTER_DISCOVERY: {
+ IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+ if (client != null) {
+ try {
+ client.onStopPrinterDiscovery();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_ON_PRINT_JOB_QUEUED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ PrintJobInfo printJob = (PrintJobInfo) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onPrintJobQueued(printJob);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
+ }
+ }
+ } break;
+
+ case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ ComponentName service = (ComponentName) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onAllPrintJobsForServiceHandled(service);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
+ }
+ }
+ } break;
+
+ case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
+ final IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+ // This has to run on the tread that is persisting the current state
+ // since this call may result in the system unbinding from the spooler
+ // and as a result the spooler process may get killed before the write
+ // completes.
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ client.onAllPrintJobsHandled();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ } break;
+ }
+ }
+ }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 26d2a33..5ff2aa6 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -114,12 +114,11 @@ public final class PrintSpoolerService extends Service {
attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
- intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
printAdapter.asBinder());
- intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
printJob.getId());
- intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_ATTRIBUTES, attributes);
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
index 25bb37c..4006a5a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
@@ -17,8 +17,8 @@
package com.android.printspooler;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
-import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.ILayoutResultCallback;
@@ -26,11 +26,7 @@ import android.print.IPrintDocumentAdapter;
import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
import android.util.Log;
-import android.util.Slog;
import libcore.io.IoUtils;
@@ -40,8 +36,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
/**
* This class represents a remote print document adapter instance.
@@ -49,461 +43,99 @@ import java.util.List;
final class RemotePrintDocumentAdapter {
private static final String LOG_TAG = "RemotePrintDocumentAdapter";
- private static final boolean DEBUG = true;
-
- public static final int STATE_INITIALIZED = 0;
- public static final int STATE_START_COMPLETED = 1;
- public static final int STATE_LAYOUT_STARTED = 2;
- public static final int STATE_LAYOUT_COMPLETED = 3;
- public static final int STATE_WRITE_STARTED = 4;
- public static final int STATE_WRITE_COMPLETED = 5;
- public static final int STATE_FINISH_COMPLETED = 6;
- public static final int STATE_FAILED = 7;
-
- private final Object mLock = new Object();
-
- private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>();
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final IPrintDocumentAdapter mRemoteInterface;
private final File mFile;
- private int mState = STATE_INITIALIZED;
-
public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
mRemoteInterface = printAdatper;
mFile = file;
}
- public File getFile() {
+ public void start() {
if (DEBUG) {
- Log.i(LOG_TAG, "getFile()");
+ Log.i(LOG_TAG, "start()");
}
- synchronized (mLock) {
- if (mState != STATE_WRITE_COMPLETED
- && mState != STATE_FINISH_COMPLETED) {
- throw new IllegalStateException("Write not completed");
- }
- return mFile;
+ try {
+ mRemoteInterface.start();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling start()", re);
}
}
- public void start() {
- QueuedAsyncTask task = new QueuedAsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "start()");
- }
- synchronized (mLock) {
- if (mState != STATE_INITIALIZED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- }
- try {
- mRemoteInterface.start();
- synchronized (mLock) {
- mState = STATE_START_COMPLETED;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error reading file", re);
- }
- return null;
- }
-
- @Override
- public void cancel() {
- /* cannot be cancelled */
- }
- };
- synchronized (mLock) {
- mTaskQueue.add(task);
- }
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- LayoutResultCallback callback, Bundle metadata) {
- LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback,
- metadata);
- synchronized (mLock) {
- mTaskQueue.add(task);
+ ILayoutResultCallback callback, Bundle metadata, int sequence) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "layout()");
}
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- public void write(List<PageRange> pages, WriteResultCallback callback) {
- WriteAsyncTask task = new WriteAsyncTask(pages, callback);
- synchronized (mLock) {
- mTaskQueue.add(task);
+ try {
+ mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling layout()", re);
}
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
- public void cancel() {
- synchronized (mLock) {
- final int taskCount = mTaskQueue.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- mTaskQueue.remove(i).cancel();
- }
+ public void write(final PageRange[] pages, final IWriteResultCallback callback,
+ final int sequence) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "write()");
}
- }
-
- public void finish() {
- QueuedAsyncTask task = new QueuedAsyncTask() {
+ new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "finish()");
- }
- synchronized (mLock) {
- if (mState < STATE_START_COMPLETED) {
- return null;
- }
- }
+ InputStream in = null;
+ OutputStream out = null;
+ ParcelFileDescriptor source = null;
+ ParcelFileDescriptor sink = null;
try {
- mRemoteInterface.finish();
- synchronized (mLock) {
- mState = STATE_FINISH_COMPLETED;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error reading file", re);
- mState = STATE_FAILED;
- }
- return null;
- }
-
- @Override
- public void cancel() {
- /* cannot be cancelled */
- }
- };
- synchronized (mLock) {
- mTaskQueue.add(task);
- }
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> {
- public void cancel() {
- super.cancel(true);
- }
- }
-
- private final class LayoutAsyncTask extends QueuedAsyncTask {
-
- private final PrintAttributes mOldAttributes;
-
- private final PrintAttributes mNewAttributes;
-
- private final LayoutResultCallback mCallback;
-
- private final Bundle mMetadata;
-
- private final ILayoutResultCallback mILayoutResultCallback =
- new ILayoutResultCallback.Stub() {
- @Override
- public void onLayoutStarted(ICancellationSignal cancellationSignal) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutStarted()");
- }
- synchronized (mLock) {
- mCancellationSignal = cancellationSignal;
- if (isCancelled()) {
- cancelSignalQuietlyLocked();
- }
- }
- }
-
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutFinished()");
- }
- final boolean cancelled;
- synchronized (mLock) {
- cancelled = isCancelled();
- mCancellationSignal = null;
- mState = STATE_LAYOUT_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- if (!cancelled) {
- mCallback.onLayoutFinished(info, changed);
- }
- }
-
- @Override
- public void onLayoutFailed(CharSequence error) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutFailed()");
- }
- final boolean cancelled;
- synchronized (mLock) {
- cancelled = isCancelled();
- mCancellationSignal = null;
- mState = STATE_LAYOUT_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- if (!cancelled) {
- mCallback.onLayoutFailed(error);
- }
- }
- };
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ source = pipe[0];
+ sink = pipe[1];
- private ICancellationSignal mCancellationSignal;
+ in = new FileInputStream(source.getFileDescriptor());
+ out = new FileOutputStream(mFile);
- public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- LayoutResultCallback callback, Bundle metadata) {
- mOldAttributes = oldAttributes;
- mNewAttributes = newAttributes;
- mCallback = callback;
- mMetadata = metadata;
- }
+ // Async call to initiate the other process writing the data.
+ mRemoteInterface.write(pages, sink, callback, sequence);
- @Override
- public void cancel() {
- synchronized (mLock) {
- throwIfCancelledLocked();
- cancelSignalQuietlyLocked();
- }
- super.cancel();
- }
+ // Close the source. It is now held by the client.
+ sink.close();
+ sink = null;
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (mLock) {
- if (DEBUG) {
- Log.i(LOG_TAG, "layout()");
- }
- if (mState != STATE_START_COMPLETED
- && mState != STATE_LAYOUT_COMPLETED
- && mState != STATE_WRITE_COMPLETED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- mState = STATE_LAYOUT_STARTED;
- }
- try {
- mRemoteInterface.layout(mOldAttributes, mNewAttributes,
- mILayoutResultCallback, mMetadata);
- synchronized (mLock) {
+ // Read the data.
+ final byte[] buffer = new byte[8192];
while (true) {
- if (mState == STATE_LAYOUT_COMPLETED) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
break;
}
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
+ out.write(buffer, 0, readByteCount);
}
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error calling layout", re);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyLayoutFailedQuietly();
- }
- return null;
- }
-
- private void cancelSignalQuietlyLocked() {
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error cancelling layout", re);
- notifyLayoutFailedQuietly();
+ Log.e(LOG_TAG, "Error calling write()", re);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error calling write()", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
}
+ return null;
}
- }
-
- public void notifyLayoutFailedQuietly() {
- try {
- mILayoutResultCallback.onLayoutFailed(null);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
-
- private void throwIfCancelledLocked() {
- if (isCancelled()) {
- throw new IllegalStateException("Already cancelled");
- }
- }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
- private final class WriteAsyncTask extends QueuedAsyncTask {
-
- private final List<PageRange> mPages;
-
- private final WriteResultCallback mCallback;
-
- private final IWriteResultCallback mIWriteResultCallback =
- new IWriteResultCallback.Stub() {
- @Override
- public void onWriteStarted(ICancellationSignal cancellationSignal) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteStarted()");
- }
- synchronized (mLock) {
- mCancellationSignal = cancellationSignal;
- if (isCancelled()) {
- cancelSignalQuietlyLocked();
- }
- }
- }
-
- @Override
- public void onWriteFinished(List<PageRange> pages) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteFinished()");
- }
- synchronized (mLock) {
- mCancellationSignal = null;
- mState = STATE_WRITE_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- mCallback.onWriteFinished(pages);
- }
-
- @Override
- public void onWriteFailed(CharSequence error) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteFailed()");
- }
- synchronized (mLock) {
- mCancellationSignal = null;
- mState = STATE_WRITE_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- Slog.e(LOG_TAG, "Error writing print document: " + error);
- mCallback.onWriteFailed(error);
- }
- };
-
- private ICancellationSignal mCancellationSignal;
-
- private Thread mWriteThread;
-
- public WriteAsyncTask(List<PageRange> pages, WriteResultCallback callback) {
- mPages = pages;
- mCallback = callback;
- }
-
- @Override
- public void cancel() {
- synchronized (mLock) {
- throwIfCancelledLocked();
- cancelSignalQuietlyLocked();
- mWriteThread.interrupt();
- }
- super.cancel();
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "write()");
- }
- synchronized (mLock) {
- if (mState != STATE_LAYOUT_COMPLETED
- && mState != STATE_WRITE_COMPLETED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- mState = STATE_WRITE_STARTED;
- }
- InputStream in = null;
- OutputStream out = null;
- ParcelFileDescriptor source = null;
- ParcelFileDescriptor sink = null;
- synchronized (mLock) {
- mWriteThread = Thread.currentThread();
- }
- try {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- source = pipe[0];
- sink = pipe[1];
-
- in = new FileInputStream(source.getFileDescriptor());
- out = new FileOutputStream(mFile);
-
- // Async call to initiate the other process writing the data.
- mRemoteInterface.write(mPages, sink, mIWriteResultCallback);
-
- // Close the source. It is now held by the client.
- sink.close();
- sink = null;
-
- final byte[] buffer = new byte[8192];
- while (true) {
- if (Thread.currentThread().isInterrupted()) {
- Thread.currentThread().interrupt();
- break;
- }
- final int readByteCount = in.read(buffer);
- if (readByteCount < 0) {
- break;
- }
- out.write(buffer, 0, readByteCount);
- }
- synchronized (mLock) {
- while (true) {
- if (mState == STATE_WRITE_COMPLETED) {
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error writing print document", re);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyWriteFailedQuietly();
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "Error writing print document", ioe);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyWriteFailedQuietly();
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(sink);
- IoUtils.closeQuietly(source);
- }
- return null;
- }
-
- private void cancelSignalQuietlyLocked() {
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error cancelling layout", re);
- notifyWriteFailedQuietly();
- }
- }
- }
-
- private void notifyWriteFailedQuietly() {
- try {
- mIWriteResultCallback.onWriteFailed(null);
- } catch (RemoteException re) {
- /* ignore */
- }
+ public void finish() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "finish()");
}
-
- private void throwIfCancelledLocked() {
- if (isCancelled()) {
- throw new IllegalStateException("Already cancelled");
- }
+ try {
+ mRemoteInterface.finish();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling finish()", re);
}
}
}
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 203bc86..a8f8566 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -28,6 +29,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.IBinder.DeathRecipient;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
@@ -46,11 +48,11 @@ import java.util.List;
* and unbinding from the remote implementation. Clients can call methods of
* this class without worrying about when and how to bind/unbind.
*/
-final class RemotePrintService {
+final class RemotePrintService implements DeathRecipient {
private static final String LOG_TAG = "RemotePrintService";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final Context mContext;
@@ -101,6 +103,15 @@ final class RemotePrintService {
mHandler.sendEmptyMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED);
}
+ @Override
+ public void binderDied() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
+ }
+
+ private void handleBinderDied() {
+ ensureBound();
+ }
+
private void handleOnAllPrintJobsHandled() {
throwIfDestroyed();
if (isBound()) {
@@ -289,6 +300,7 @@ final class RemotePrintService {
public static final int MSG_START_PRINTER_DISCOVERY = 4;
public static final int MSG_STOP_PRINTER_DISCOVERY = 5;
public static final int MSG_DESTROY = 6;
+ public static final int MSG_BINDER_DIED = 7;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -324,6 +336,10 @@ final class RemotePrintService {
case MSG_DESTROY: {
handleDestroy();
} break;
+
+ case MSG_BINDER_DIED: {
+ handleBinderDied();
+ } break;
}
}
}
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index 6445c2e..ded410b 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -54,7 +55,7 @@ final class RemotePrintSpooler {
private static final String LOG_TAG = "RemotePrintSpooler";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
@@ -88,6 +89,8 @@ final class RemotePrintSpooler {
private boolean mDestroyed;
+ private boolean mCanUnbind;
+
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
@@ -111,6 +114,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
@@ -122,6 +126,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print jobs.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print jobs.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -131,6 +140,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
@@ -142,6 +152,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error creating print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error creating print job.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -150,6 +165,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()");
@@ -161,6 +177,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error canceling print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error canceling print job.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -169,6 +190,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
@@ -183,6 +205,10 @@ final class RemotePrintSpooler {
// We passed the file descriptor across and now the other
// side is responsible to close it, so close the local copy.
IoUtils.closeQuietly(fd);
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
}
@@ -190,6 +216,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
@@ -201,6 +228,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print job info.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print job info.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -209,6 +241,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
@@ -220,6 +253,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job state.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job state.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -228,6 +266,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
@@ -239,6 +278,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job tag.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job tag.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -247,6 +291,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
@@ -258,6 +303,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error asking for active print job notification.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error asking for active print job notification.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
}
@@ -270,6 +320,7 @@ final class RemotePrintSpooler {
throwIfDestroyedLocked();
unbindLocked();
mDestroyed = true;
+ mCanUnbind = false;
}
}
@@ -314,15 +365,29 @@ final class RemotePrintSpooler {
/* ignore */
}
}
+
+ mCanUnbind = true;
+ mLock.notifyAll();
}
private void unbindLocked() {
- if (DEBUG) {
- Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+ while (true) {
+ if (mCanUnbind) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+ }
+ clearClientLocked();
+ mRemoteInstance = null;
+ mContext.unbindService(mServiceConnection);
+ return;
+ }
+ try {
+ mLock.wait();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
}
- clearClientLocked();
- mRemoteInstance = null;
- mContext.unbindService(mServiceConnection);
+
}
private void setClientLocked() {