summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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() {