diff options
51 files changed, 3442 insertions, 2228 deletions
diff --git a/Android.mk b/Android.mk index db6a7049f6a7..d81e58118db0 100644 --- a/Android.mk +++ b/Android.mk @@ -159,13 +159,15 @@ LOCAL_SRC_FILES += \ core/java/android/os/IUserManager.aidl \ core/java/android/os/IVibratorService.aidl \ core/java/android/service/notification/INotificationListener.aidl \ + core/java/android/print/ILayoutResultCallback.aidl \ + core/java/android/print/IPrintDocumentAdapter.aidl \ core/java/android/print/IPrinterDiscoveryObserver.aidl \ - core/java/android/print/IPrintAdapter.aidl \ core/java/android/print/IPrintClient.aidl \ - core/java/android/print/IPrintResultCallback.aidl \ core/java/android/print/IPrintManager.aidl \ - core/java/android/print/IPrintSpoolerService.aidl \ - core/java/android/print/IPrintSpoolerServiceCallbacks.aidl \ + core/java/android/print/IPrintSpooler.aidl \ + core/java/android/print/IPrintSpoolerCallbacks.aidl \ + core/java/android/print/IPrintSpoolerClient.aidl \ + core/java/android/print/IWriteResultCallback.aidl \ core/java/android/printservice/IPrintService.aidl \ core/java/android/printservice/IPrintServiceClient.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ diff --git a/api/current.txt b/api/current.txt index 7196161174b2..ca6dff97f84a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18448,36 +18448,6 @@ package android.print { field public static final android.os.Parcelable.Creator CREATOR; } - public abstract class PrintAdapter { - ctor public PrintAdapter(); - method public abstract android.print.PrintAdapterInfo getInfo(); - method public void onFinish(); - method public abstract void onPrint(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintAdapter.PrintResultCallback); - method public boolean onPrintAttributesChanged(android.print.PrintAttributes); - method public void onStart(); - } - - public static abstract class PrintAdapter.PrintResultCallback { - method public void onPrintFailed(java.lang.CharSequence); - method public void onPrintFinished(java.util.List<android.print.PageRange>); - } - - public final class PrintAdapterInfo implements android.os.Parcelable { - method public int describeContents(); - method public int getFlags(); - method public int getPageCount(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final int PAGE_COUNT_UNKNOWN = -1; // 0xffffffff - } - - public static final class PrintAdapterInfo.Builder { - ctor public PrintAdapterInfo.Builder(); - method public android.print.PrintAdapterInfo create(); - method public android.print.PrintAdapterInfo.Builder setFlags(int); - method public android.print.PrintAdapterInfo.Builder setPageCount(int); - } - public final class PrintAttributes implements android.os.Parcelable { method public void clear(); method public int describeContents(); @@ -18588,6 +18558,43 @@ package android.print { method public java.lang.CharSequence getLabel(android.content.pm.PackageManager); } + public abstract class PrintDocumentAdapter { + ctor public PrintDocumentAdapter(); + method public void onFinish(); + method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback); + 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); + } + + public static abstract class PrintDocumentAdapter.LayoutResultCallback { + method public void onLayoutFailed(java.lang.CharSequence); + method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean); + } + + public static abstract class PrintDocumentAdapter.WriteResultCallback { + method public void onWriteFailed(java.lang.CharSequence); + method public void onWriteFinished(java.util.List<android.print.PageRange>); + } + + public final class PrintDocumentInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getContentType(); + method public int getPageCount(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CONTENT_TYPE_DOCUMENT = 0; // 0x0 + field public static final int CONTENT_TYPE_PHOTO = 1; // 0x1 + field public static final int CONTENT_TYPE_UNKNOWN = -1; // 0xffffffff + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int PAGE_COUNT_UNKNOWN = -1; // 0xffffffff + } + + public static final class PrintDocumentInfo.Builder { + ctor public PrintDocumentInfo.Builder(); + method public android.print.PrintDocumentInfo create(); + method public android.print.PrintDocumentInfo.Builder setContentType(int); + method public android.print.PrintDocumentInfo.Builder setPageCount(int); + } + public final class PrintJob { method public void cancel(); method public int getId(); @@ -18617,7 +18624,7 @@ package android.print { public final class PrintManager { method public java.util.List<android.print.PrintJob> getPrintJobs(); method public android.print.PrintJob print(java.lang.String, java.io.File, android.print.PrintAttributes); - method public android.print.PrintJob print(java.lang.String, android.print.PrintAdapter, android.print.PrintAttributes); + method public android.print.PrintJob print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes); } public final class PrinterId implements android.os.Parcelable { @@ -18699,11 +18706,16 @@ package android.print.pdf { package android.printservice { + public final class PrintDocument { + method public java.io.FileDescriptor getData(); + method public android.print.PrintDocumentInfo getInfo(); + } + public final class PrintJob { method public boolean cancel(); method public boolean complete(); method public boolean fail(java.lang.CharSequence); - method public final java.io.FileDescriptor getData(); + method public android.printservice.PrintDocument getDocument(); method public int getId(); method public android.print.PrintJobInfo getInfo(); method public boolean isQueued(); diff --git a/core/java/android/print/PrintFileAdapter.java b/core/java/android/print/FileDocumentAdapter.java index dab964846552..d162c19fdb37 100644 --- a/core/java/android/print/PrintFileAdapter.java +++ b/core/java/android/print/FileDocumentAdapter.java @@ -36,15 +36,15 @@ import java.util.List; /** * Adapter for printing files. */ -class PrintFileAdapter extends PrintAdapter { +final class FileDocumentAdapter extends PrintDocumentAdapter { - private static final String LOG_TAG = "PrintFileAdapter"; + private static final String LOG_TAG = "FileDocumentAdapter"; private final File mFile; private WriteFileAsyncTask mWriteFileAsyncTask; - public PrintFileAdapter(File file) { + public FileDocumentAdapter(File file) { if (file == null) { throw new IllegalArgumentException("File cannot be null!"); } @@ -52,8 +52,17 @@ class PrintFileAdapter extends PrintAdapter { } @Override - public void onPrint(List<PageRange> pages, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback callback) { + public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback) { + // TODO: When we have a PDF rendering library we should query the page count. + PrintDocumentInfo info = new PrintDocumentInfo.Builder() + .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN).create(); + callback.onLayoutFinished(info, false); + } + + @Override + public void onWrite(List<PageRange> pages, FileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal, callback); mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, @@ -61,24 +70,18 @@ class PrintFileAdapter extends PrintAdapter { } - @Override - public PrintAdapterInfo getInfo() { - // TODO: When we have PDF render library we should query the page count. - return new PrintAdapterInfo.Builder().create(); - } - private static final class WriteFileAsyncTask extends AsyncTask<Void, Void, Void> { private final File mSource; private final FileDescriptor mDestination; - private final PrintResultCallback mResultCallback; + private final WriteResultCallback mResultCallback; private final CancellationSignal mCancellationSignal; public WriteFileAsyncTask(File source, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback callback) { + CancellationSignal cancellationSignal, WriteResultCallback callback) { mSource = source; mDestination = destination; mResultCallback = callback; @@ -113,9 +116,9 @@ class PrintFileAdapter extends PrintAdapter { if (!isCancelled()) { List<PageRange> pages = new ArrayList<PageRange>(); pages.add(PageRange.ALL_PAGES); - mResultCallback.onPrintFinished(pages); + mResultCallback.onWriteFinished(pages); } else { - mResultCallback.onPrintFailed("Cancelled"); + mResultCallback.onWriteFailed("Cancelled"); } } return null; diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl new file mode 100644 index 000000000000..e4d79f3057ba --- /dev/null +++ b/core/java/android/print/ILayoutResultCallback.aidl @@ -0,0 +1,31 @@ +/* + * 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.os.ICancellationSignal; +import android.print.PrintDocumentInfo; + +/** + * Callback for observing the result of android.print.PrintAdapter#onLayout. + * + * @hide + */ +oneway interface ILayoutResultCallback { + void onLayoutStarted(ICancellationSignal cancellationSignal); + void onLayoutFinished(in PrintDocumentInfo info, boolean changed); + void onLayoutFailed(CharSequence error); +} diff --git a/core/java/android/print/IPrintAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index f3ff8c4d0ce8..36938e3592cc 100644 --- a/core/java/android/print/IPrintAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -17,7 +17,8 @@ package android.print; import android.os.ParcelFileDescriptor; -import android.print.IPrintResultCallback; +import android.print.ILayoutResultCallback; +import android.print.IWriteResultCallback; import android.print.PageRange; import android.print.PrintAttributes; @@ -26,10 +27,11 @@ import android.print.PrintAttributes; * * @hide */ -oneway interface IPrintAdapter { +oneway interface IPrintDocumentAdapter { void start(); - void printAttributesChanged(in PrintAttributes attributes); - void print(in List<PageRange> pages, in ParcelFileDescriptor fd, - IPrintResultCallback callback); + void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes, + ILayoutResultCallback callback); + void write(in List<PageRange> pages, in ParcelFileDescriptor fd, + IWriteResultCallback callback); void finish(); } diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index ff9877e04980..a466e741bc90 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -16,11 +16,8 @@ package android.print; -import android.os.ICancellationSignal; -import android.print.IPrintAdapter; +import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; -import android.print.IPrinterDiscoveryObserver; -import android.print.PrinterId; import android.print.PrintJobInfo; import android.print.PrintAttributes; @@ -30,12 +27,11 @@ import android.print.PrintAttributes; * @hide */ interface IPrintManager { - List<PrintJobInfo> getPrintJobs(int appId, int userId); - PrintJobInfo getPrintJob(int printJobId, int appId, int userId); - PrintJobInfo print(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter, - in PrintAttributes attributes, int appId, int userId); + List<PrintJobInfo> getPrintJobInfos(int appId, int userId); + PrintJobInfo getPrintJobInfo(int printJobId, int appId, int userId); + PrintJobInfo print(String printJobName, in IPrintClient client, + in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes, + int appId, int userId); void cancelPrintJob(int printJobId, int appId, int userId); - void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob); - void startDiscoverPrinters(IPrinterDiscoveryObserver observer); - void stopDiscoverPrinters(); + } diff --git a/core/java/android/print/IPrintSpoolerService.aidl b/core/java/android/print/IPrintSpooler.aidl index e84d5927fe0c..c55205d0f1ff 100644 --- a/core/java/android/print/IPrintSpoolerService.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -18,32 +18,35 @@ package android.print; import android.content.ComponentName; import android.os.ParcelFileDescriptor; -import android.print.IPrintAdapter; +import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; -import android.print.IPrintSpoolerServiceCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.IPrintSpoolerCallbacks; import android.print.PrinterInfo; import android.print.PrintAttributes; /** * Interface for communication with the print spooler service. * - * @see android.print.IPrintSpoolerServiceCallbacks + * @see android.print.IPrintSpoolerCallbacks * * @hide */ -oneway interface IPrintSpoolerService { - void getPrintJobs(IPrintSpoolerServiceCallbacks callback, in ComponentName componentName, +oneway interface IPrintSpooler { + void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName, int state, int appId, int sequence); - void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + void getPrintJobInfo(int printJobId, IPrintSpoolerCallbacks callback, int appId, int sequence); - void createPrintJob(String printJobName, in IPrintClient client, in IPrintAdapter printAdapter, - in PrintAttributes attributes, IPrintSpoolerServiceCallbacks callback, int appId, - int sequence); - void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + void createPrintJob(String printJobName, in IPrintClient client, + in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes, + IPrintSpoolerCallbacks callback, int appId, int sequence); + void cancelPrintJob(int printJobId, IPrintSpoolerCallbacks callback, int appId, int sequence); - void setPrintJobState(int printJobId, int status, IPrintSpoolerServiceCallbacks callback, + void setPrintJobState(int printJobId, int status, IPrintSpoolerCallbacks callback, int sequence); - void setPrintJobTag(int printJobId, String tag, IPrintSpoolerServiceCallbacks callback, + void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback, int sequence); void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); -}
\ No newline at end of file + void setClient(IPrintSpoolerClient client); + void notifyClientForActivteJobs(); +} diff --git a/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl index 0c519136bb83..7912964efecc 100644 --- a/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl +++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl @@ -26,8 +26,8 @@ import java.util.List; * * @hide */ -oneway interface IPrintSpoolerServiceCallbacks { - void onGetPrintJobsResult(in List<PrintJobInfo> printJob, int sequence); +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); diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl new file mode 100644 index 000000000000..47975e1eafa9 --- /dev/null +++ b/core/java/android/print/IPrintSpoolerClient.aidl @@ -0,0 +1,35 @@ +/* + * 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.content.ComponentName; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintJobInfo; + + +/** + * Interface for receiving interesting state updates from the print spooler. + * + * @hide + */ +oneway interface IPrintSpoolerClient { + void onPrintJobQueued(in PrintJobInfo printJob); + void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer); + void onStopPrinterDiscovery(); + void onAllPrintJobsForServiceHandled(in ComponentName printService); + void onAllPrintJobsHandled(); +} diff --git a/core/java/android/print/IPrintSpoolerObserver.aidl b/core/java/android/print/IPrintSpoolerObserver.aidl new file mode 100644 index 000000000000..7b8f40e4ef10 --- /dev/null +++ b/core/java/android/print/IPrintSpoolerObserver.aidl @@ -0,0 +1,31 @@ +/* + * 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/IPrintResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl index 838377ea4a96..d5428b145aff 100644 --- a/core/java/android/print/IPrintResultCallback.aidl +++ b/core/java/android/print/IWriteResultCallback.aidl @@ -18,16 +18,14 @@ package android.print; import android.os.ICancellationSignal; import android.print.PageRange; -import android.print.PrintAdapterInfo; /** - * Callbacks for observing the print progress (writing of printed content) - * of a PrintAdapter. + * Callback for observing the result of android.print.DocuemntAdapter#onWrite. * * @hide */ -oneway interface IPrintResultCallback { - void onPrintStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal); - void onPrintFinished(in List<PageRange> pages); - void onPrintFailed(CharSequence error); +oneway interface IWriteResultCallback { + void onWriteStarted(ICancellationSignal cancellationSignal); + void onWriteFinished(in List<PageRange> pages); + void onWriteFailed(CharSequence error); } diff --git a/core/java/android/print/PrintAdapter.java b/core/java/android/print/PrintAdapter.java deleted file mode 100644 index 6547c55e1d1a..000000000000 --- a/core/java/android/print/PrintAdapter.java +++ /dev/null @@ -1,164 +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.os.CancellationSignal; - -import java.io.FileDescriptor; -import java.util.List; - -/** - * Base class that provides data to be printed. - * - * <h3>Lifecycle</h3> - * <p> - * <ul> - * <li> - * You will receive a call on {@link #onStart()} when printing starts. - * This callback can be used to allocate resources. - * </li> - * <li> - * Next you will get one or more calls to {@link #onPrintAttributesChanged( - * PrintAttributes) to informs you that the print attributes (page size, density, - * etc) changed giving you an opportunity to re-layout the content. - * </li> - * <li> - * After every {@link #onPrintAttributesChanged(PrintAttributes) you will receive - * one or more calls to {@link #onPrint(List, FileDescriptor, CancellationSignal, - * PrintResultCallback)} asking you to write a PDF file with the content for - * specific pages. - * </li> - * <li> - * Finally, you will receive a call on {@link #onFinish()} right after printing. - * You can use this callback to release resources. - * </li> - * <li> - * You can receive calls to {@link #getInfo()} at any point after a call to - * {@link #onPrintAttributesChanged(PrintAttributes)} which should return - * a {@link PrintAdapterInfo} describing your {@link PrintAdapter}. - * </li> - * </ul> - * </p> - * <p> - */ -public abstract class PrintAdapter { - - /** - * Called when printing started. You can use this callback to - * allocate resources. - * <p> - * <strong>Note:</strong> Invoked on the main thread. - * </p> - */ - public void onStart() { - /* do nothing - stub */ - } - - /** - * Called when the print job attributes (page size, density, etc) - * changed giving you a chance to re-layout the content such that - * it matches the new constraints. - * <p> - * <strong>Note:</strong> Invoked on the main thread. - * </p> - * - * @param attributes The print job attributes. - * @return Whether the content changed based on the provided attributes. - */ - public boolean onPrintAttributesChanged(PrintAttributes attributes) { - return false; - } - - /** - * Called when specific pages of the content have to be printed in the from of - * a PDF file to the given file descriptor. You should <strong>not</strong> - * close the file descriptor instead you have to invoke {@link PrintResultCallback - * #onPrintFinished()} or {@link PrintResultCallback#onPrintFailed(CharSequence)}. - * <p> - * <strong>Note:</strong> If the printed content is large, it is a good - * practice to schedule writing it on a dedicated thread and register a - * callback in the provided {@link CancellationSignal} upon invocation of - * which you should stop writing data. The cancellation callback will not - * be made on the main thread. - * </p> - * <p> - * <strong>Note:</strong> Invoked on the main thread. - * </p> - * - * @param pages The pages whose content to print. - * @param destination The destination file descriptor to which to start writing. - * @param cancellationSignal Signal for observing cancel print requests. - * @param progressListener Callback to inform the system with the write progress. - * - * @see CancellationSignal - */ - public abstract void onPrint(List<PageRange> pages, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback progressListener); - - /** - * Called when printing finished. You can use this callback to release - * resources. - * <p> - * <strong>Note:</strong> Invoked on the main thread. - * </p> - */ - public void onFinish() { - /* do nothing - stub */ - } - - /** - * Gets a {@link PrinterInfo} object that contains metadata about the - * printed content. - * <p> - * <strong>Note:</strong> Invoked on the main thread. - * </p> - * - * @return The info object for this {@link PrintAdapter}. - * - * @see PrintAdapterInfo - */ - public abstract PrintAdapterInfo getInfo(); - - /** - * Base class for implementing a listener for the print result - * of a {@link PrintAdapter}. - */ - public static abstract class PrintResultCallback { - - PrintResultCallback() { - /* do nothing - hide constructor */ - } - - /** - * Notifies that all the data was printed. - * - * @param pages The pages that were printed. - */ - public void onPrintFinished(List<PageRange> pages) { - /* do nothing - stub */ - } - - /** - * Notifies that an error occurred while printing the data. - * - * @param error Error message. May be null if error is unknown. - */ - public void onPrintFailed(CharSequence error) { - /* do nothing - stub */ - } - } -} diff --git a/core/java/android/print/PrintAdapterInfo.java b/core/java/android/print/PrintAdapterInfo.java deleted file mode 100644 index 06e6b10f3b48..000000000000 --- a/core/java/android/print/PrintAdapterInfo.java +++ /dev/null @@ -1,136 +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.os.Parcel; -import android.os.Parcelable; - -/** - * This class encapsulates information about a {@link PrintAdapter} object. - */ -public final class PrintAdapterInfo implements Parcelable { - - /** - * Constant for unknown page count. - */ - public static final int PAGE_COUNT_UNKNOWN = -1; - - private int mPageCount; - private int mFlags; - - /** - * Creates a new instance. - */ - private PrintAdapterInfo() { - /* do nothing */ - } - - /** - * Creates a new instance. - * - * @param parcel Data from which to initialize. - */ - private PrintAdapterInfo(Parcel parcel) { - mPageCount = parcel.readInt(); - mFlags = parcel.readInt(); - } - - /** - * Gets the total number of pages. - * - * @return The number of pages. - */ - public int getPageCount() { - return mPageCount; - } - - /** - * @return The flags of this printable info. - * - * @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE - */ - public int getFlags() { - return mFlags; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeInt(mPageCount); - parcel.writeInt(mFlags); - } - - /** - * Builder for creating an {@link PrintAdapterInfo}. - */ - public static final class Builder { - private final PrintAdapterInfo mPrintableInfo = new PrintAdapterInfo(); - - /** - * Sets the total number of pages. - * - * @param pageCount The number of pages. Must be - * greater than zero. - */ - public Builder setPageCount(int pageCount) { - if (pageCount < 0) { - throw new IllegalArgumentException("pageCount" - + " must be greater than or euqal to zero!"); - } - mPrintableInfo.mPageCount = pageCount; - return this; - } - - /** - * Sets the flags of this printable info. - * - * @param flags The flags. - * - * @see #FLAG_NOTIFY_FOR_ATTRIBUTES_CHANGE - */ - public Builder setFlags(int flags) { - mPrintableInfo.mFlags = flags; - return this; - } - - /** - * Creates a new {@link PrintAdapterInfo} instance. - * - * @return The new instance. - */ - public PrintAdapterInfo create() { - return mPrintableInfo; - } - } - - public static final Parcelable.Creator<PrintAdapterInfo> CREATOR = - new Creator<PrintAdapterInfo>() { - @Override - public PrintAdapterInfo createFromParcel(Parcel parcel) { - return new PrintAdapterInfo(parcel); - } - - @Override - public PrintAdapterInfo[] newArray(int size) { - return new PrintAdapterInfo[size]; - } - }; -} diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java new file mode 100644 index 000000000000..ef69400c3628 --- /dev/null +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -0,0 +1,200 @@ +/* + * 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.os.CancellationSignal; + +import java.io.FileDescriptor; +import java.util.List; + +/** + * Base class that provides the content of a document to be printed. + * + * <h3>Lifecycle</h3> + * <p> + * <ul> + * <li> + * Initially, you will receive a call to {@link #onStart()}. This callback + * can be used to allocate resources. + * </li> + * <li> + * Next, you will get one or more calls to {@link #onLayout(PrintAttributes, + * PrintAttributes, CancellationSignal, LayoutResultCallback)} to inform you + * that the print attributes (page size, density, etc) changed giving you an + * opportunity to layout the content to match the new constraints. + * </li> + * <li> + * After every call to {@link #onLayout(PrintAttributes, PrintAttributes, + * CancellationSignal, LayoutResultCallback)}, you may get a call to {@link + * #onWrite(List, FileDescriptor, CancellationSignal, WriteResultCallback)} + * asking you to write a PDF file with the content for specific pages. + * </li> + * <li> + * Finally, you will receive a call to {@link #onFinish()}. You can use this + * callback to release resources allocated in {@link #onStart()}. + * </li> + * </ul> + * </p> + */ +public abstract class PrintDocumentAdapter { + + /** + * Called when printing starts. You can use this callback to allocate + * resources. This method is invoked on the main thread. + */ + public void onStart() { + /* do nothing - stub */ + } + + /** + * Called when the print attributes (page size, density, etc) changed + * giving you a chance to layout the content such that it matches the + * new constraints. This method is invoked on the main thread. + * <p> + * After you are done laying out, you must invoke: {@link LayoutResultCallback + * #onLayoutFinished(PrintDocumentInfo, boolean)} with the last argument <code>true + * </code> or <code>false</code> depending on whether the layout changed the + * content or not, respectively; and {@link LayoutResultCallback#onLayoutFailed( + * CharSequence), if an error occurred. + * </p> + * <p> + * <strong>Note:</strong> If the content is large and a layout will be + * performed, it is a good practice to schedule the work on a dedicated + * thread and register an observer in the provided {@link + * CancellationSignal} upon invocation of which you should stop the + * layout. The cancellation callback will not be made on the main + * thread. + * </p> + * + * @param oldAttributes The old print attributes. + * @param newAttributes The new print attributes. + * @param cancellationSignal Signal for observing cancel layout requests. + * @param callback Callback to inform the system for the layout result. + * + * @see LayoutResultCallback + * @see CancellationSignal + */ + public abstract void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + CancellationSignal cancellationSignal, LayoutResultCallback callback); + + /** + * Called when specific pages of the content should be written in the + * from of a PDF file to the given file descriptor. This method is invoked + * on the main thread. + *<p> + * After you are done writing, you should <strong>not</strong> close the + * file descriptor, rather you must invoke: {@link WriteResultCallback + * #onWriteFinished()}, if writing completed successfully; or {@link + * WriteResultCallback#onWriteFailed(CharSequence)}, if an error occurred. + * </p> + * <p> + * <strong>Note:</strong> If the printed content is large, it is a good + * practice to schedule writing it on a dedicated thread and register an + * observer in the provided {@link CancellationSignal} upon invocation of + * which you should stop writing. The cancellation callback will not be + * made on the main thread. + * </p> + * + * @param pages The pages whose content to print. + * @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. + * + * @see WriteResultCallback + * @see CancellationSignal + */ + public abstract void onWrite(List<PageRange> pages, FileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback); + + /** + * Called when printing finishes. You can use this callback to release + * resources acquired in {@link #onStart()}. This method is invoked on + * the main thread. + */ + public void onFinish() { + /* do nothing - stub */ + } + + /** + * Base class for implementing a callback for the result of {@link + * PrintDocumentAdapter#onWrite(List, FileDescriptor, CancellationSignal, + * WriteResultCallback)}. + */ + public static abstract class WriteResultCallback { + + /** + * @hide + */ + public WriteResultCallback() { + /* do nothing - hide constructor */ + } + + /** + * Notifies that all the data was written. + * + * @param pages The pages that were written. + */ + public void onWriteFinished(List<PageRange> pages) { + /* do nothing - stub */ + } + + /** + * Notifies that an error occurred while writing the data. + * + * @param error Error message. May be null if error is unknown. + */ + public void onWriteFailed(CharSequence error) { + /* do nothing - stub */ + } + } + + /** + * Base class for implementing a callback for the result of {@link + * PrintDocumentAdapter#onLayout(PrintAttributes, PrintAttributes, + * CancellationSignal, LayoutResultCallback)}. + */ + public static abstract class LayoutResultCallback { + + /** + * @hide + */ + public LayoutResultCallback() { + /* do nothing - hide constructor */ + } + + /** + * Notifies that the layout finished and whether the content changed. + * + * @param info An info object describing the document. + * @param changed Whether the layout changed. + * + * @see PrintDocumentInfo + */ + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + /* do nothing - stub */ + } + + /** + * Notifies that an error occurred while laying out the document. + * + * @param error Error message. May be null if error is unknown. + */ + public void onLayoutFailed(CharSequence error) { + /* do nothing - stub */ + } + } +} diff --git a/core/java/android/print/PrintAdapterInfo.aidl b/core/java/android/print/PrintDocumentInfo.aidl index 27bf717f6d20..831dcb7ba1ae 100644 --- a/core/java/android/print/PrintAdapterInfo.aidl +++ b/core/java/android/print/PrintDocumentInfo.aidl @@ -16,4 +16,4 @@ package android.print; -parcelable PrintAdapterInfo; +parcelable PrintDocumentInfo; diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java new file mode 100644 index 000000000000..7731debfa7f2 --- /dev/null +++ b/core/java/android/print/PrintDocumentInfo.java @@ -0,0 +1,171 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +/** + * This class encapsulates information about a printed document. + */ +public final class PrintDocumentInfo implements Parcelable { + + /** + * Constant for unknown page count (default). + */ + public static final int PAGE_COUNT_UNKNOWN = -1; + + /** + * Content type: unknown (default). + */ + public static final int CONTENT_TYPE_UNKNOWN = -1; + + /** + * Content type: document. + */ + public static final int CONTENT_TYPE_DOCUMENT = 0; + + /** + * Content type: photo. + */ + public static final int CONTENT_TYPE_PHOTO = 1; + + private int mPageCount; + private int mContentType; + + /** + * Creates a new instance. + */ + private PrintDocumentInfo() { + mPageCount = PAGE_COUNT_UNKNOWN; + mContentType = CONTENT_TYPE_UNKNOWN; + } + + /** + * Creates a new instance. + * + * @param Prototype from which to clone. + */ + private PrintDocumentInfo(PrintDocumentInfo prototype) { + mPageCount = prototype.mPageCount; + mContentType = prototype.mContentType; + } + + /** + * Creates a new instance. + * + * @param parcel Data from which to initialize. + */ + private PrintDocumentInfo(Parcel parcel) { + mPageCount = parcel.readInt(); + mContentType = parcel.readInt(); + } + + /** + * Gets the total number of pages. + * + * @return The number of pages. + * + * @see #PAGE_COUNT_UNKNOWN + */ + public int getPageCount() { + return mPageCount; + } + + /** + * Gets the content type. + * + * @return The content type. + * + * @see #CONTENT_TYPE_UNKNOWN + * @see #CONTENT_TYPE_DOCUMENT + * @see #CONTENT_TYPE_PHOTO + */ + public int getContentType() { + return mContentType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mPageCount); + parcel.writeInt(mContentType); + } + + /** + * Builder for creating an {@link PrintDocumentInfo}. + */ + public static final class Builder { + private final PrintDocumentInfo mPrototype = new PrintDocumentInfo(); + + /** + * Sets the total number of pages. + * + * @param pageCount The number of pages. Must be greater than + * or equal to zero or {@link PrintDocumentInfo#PAGE_COUNT_UNKNOWN}. + */ + public Builder setPageCount(int pageCount) { + if (pageCount < 0 && pageCount != PAGE_COUNT_UNKNOWN) { + throw new IllegalArgumentException("pageCount" + + " must be greater than or euqal to zero or" + + " DocumentInfo#PAGE_COUNT_UNKNOWN"); + } + mPrototype.mPageCount = pageCount; + return this; + } + + /** + * Sets the content type. + * + * @param type The content type. + * + * @see #CONTENT_TYPE_UNKNOWN + * @see #CONTENT_TYPE_DOCUMENT + * @see #CONTENT_TYPE_PHOTO + */ + public Builder setContentType(int type) { + mPrototype.mContentType = type; + return this; + } + + /** + * Creates a new {@link PrintDocumentInfo} instance. + * + * @return The new instance. + */ + public PrintDocumentInfo create() { + return new PrintDocumentInfo(mPrototype); + } + } + + public static final Parcelable.Creator<PrintDocumentInfo> CREATOR = + new Creator<PrintDocumentInfo>() { + @Override + public PrintDocumentInfo createFromParcel(Parcel parcel) { + return new PrintDocumentInfo(parcel); + } + + @Override + public PrintDocumentInfo[] newArray(int size) { + return new PrintDocumentInfo[size]; + } + }; +} diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java index f7cca875a2c9..a5e0b796802a 100644 --- a/core/java/android/print/PrintJob.java +++ b/core/java/android/print/PrintJob.java @@ -55,7 +55,7 @@ public final class PrintJob { * @return The print job info. */ public PrintJobInfo getInfo() { - PrintJobInfo info = mPrintManager.getPrintJob(mId); + PrintJobInfo info = mPrintManager.getPrintJobInfo(mId); if (info != null) { mCachedInfo = info; } diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java index 72d6057e5323..6e613bcd981f 100644 --- a/core/java/android/print/PrintJobInfo.java +++ b/core/java/android/print/PrintJobInfo.java @@ -116,6 +116,44 @@ public final class PrintJobInfo implements Parcelable { /** The print job attributes size. */ private PrintAttributes mAttributes; + /** Information about the printed document. */ + private PrintDocumentInfo mDocumentInfo; + + /** @hide*/ + public PrintJobInfo() { + /* do nothing */ + } + + /** @hide */ + public PrintJobInfo(PrintJobInfo other) { + mId = other.mId; + mLabel = other.mLabel; + mPrinterId = other.mPrinterId; + mState = other.mState; + mAppId = other.mAppId; + mUserId = other.mUserId; + mAttributes = other.mAttributes; + mDocumentInfo = other.mDocumentInfo; + } + + private PrintJobInfo(Parcel parcel) { + mId = parcel.readInt(); + mLabel = parcel.readCharSequence(); + mPrinterId = parcel.readParcelable(null); + mState = parcel.readInt(); + mAppId = parcel.readInt(); + mUserId = parcel.readInt(); + if (parcel.readInt() == 1) { + mPageRanges = (PageRange[]) parcel.readParcelableArray(null); + } + if (parcel.readInt() == 1) { + mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel); + } + if (parcel.readInt() == 1) { + mDocumentInfo = PrintDocumentInfo.CREATOR.createFromParcel(parcel); + } + } + /** * Gets the unique print job id. * @@ -300,35 +338,26 @@ public final class PrintJobInfo implements Parcelable { mAttributes = attributes; } - /** @hide*/ - public PrintJobInfo() { - /* do nothing */ - } - - /** @hide */ - public PrintJobInfo(PrintJobInfo other) { - mId = other.mId; - mLabel = other.mLabel; - mPrinterId = other.mPrinterId; - mState = other.mState; - mAppId = other.mAppId; - mUserId = other.mUserId; - mAttributes = other.mAttributes; + /** + * Gets the info describing the printed document. + * + * @return The document info. + * + * @hide + */ + public PrintDocumentInfo getDocumentInfo() { + return mDocumentInfo; } - private PrintJobInfo(Parcel parcel) { - mId = parcel.readInt(); - mLabel = parcel.readCharSequence(); - mPrinterId = parcel.readParcelable(null); - mState = parcel.readInt(); - mAppId = parcel.readInt(); - mUserId = parcel.readInt(); - if (parcel.readInt() == 1) { - mPageRanges = (PageRange[]) parcel.readParcelableArray(null); - } - if (parcel.readInt() == 1) { - mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel); - } + /** + * Sets the info describing the printed document. + * + * @param info The document info. + * + * @hide + */ + public void setDocumentInfo(PrintDocumentInfo info) { + mDocumentInfo = info; } @Override @@ -356,6 +385,12 @@ public final class PrintJobInfo implements Parcelable { } else { parcel.writeInt(0); } + if (mDocumentInfo != null) { + parcel.writeInt(1); + mDocumentInfo.writeToParcel(parcel, flags); + } else { + parcel.writeInt(0); + } } @Override @@ -366,7 +401,10 @@ public final class PrintJobInfo implements Parcelable { builder.append(", id: ").append(mId); builder.append(", status: ").append(stateToString(mState)); builder.append(", printer: " + mPrinterId); - builder.append(", attributes: " + (mAttributes != null ? mAttributes.toString() : null)); + builder.append(", attributes: " + (mAttributes != null + ? mAttributes.toString() : null)); + builder.append(", documentInfo: " + (mDocumentInfo != null + ? mDocumentInfo.toString() : null)); builder.append("}"); return builder.toString(); } diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index be9b59658c7b..8913daa9650b 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -26,7 +26,8 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.print.PrintAdapter.PrintResultCallback; +import android.print.PrintDocumentAdapter.LayoutResultCallback; +import android.print.PrintDocumentAdapter.WriteResultCallback; import android.util.Log; import com.android.internal.os.SomeArgs; @@ -103,7 +104,7 @@ public final class PrintManager { * Creates an instance that can access all print jobs. * * @param userId The user id for which to get all print jobs. - * @return An instance of the caller has the permission to access + * @return An instance if the caller has the permission to access * all print jobs, null otherwise. * * @hide @@ -112,11 +113,11 @@ public final class PrintManager { return new PrintManager(mContext, mService, userId, APP_ID_ANY); } - PrintJobInfo getPrintJob(int printJobId) { + PrintJobInfo getPrintJobInfo(int printJobId) { try { - return mService.getPrintJob(printJobId, mAppId, mUserId); + return mService.getPrintJobInfo(printJobId, mAppId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error getting print job:" + printJobId, re); + Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); } return null; } @@ -130,7 +131,7 @@ public final class PrintManager { */ public List<PrintJob> getPrintJobs() { try { - List<PrintJobInfo> printJobInfos = mService.getPrintJobs(mAppId, mUserId); + List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); if (printJobInfos == null) { return Collections.emptyList(); } @@ -141,18 +142,17 @@ public final class PrintManager { } return printJobs; } catch (RemoteException re) { - Log.e(LOG_TAG, "Error getting print jobs!", re); + Log.e(LOG_TAG, "Error getting print jobs", re); } return Collections.emptyList(); } - ICancellationSignal cancelPrintJob(int printJobId) { + void cancelPrintJob(int printJobId) { try { mService.cancelPrintJob(printJobId, mAppId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error cancleing a print job:" + printJobId, re); + Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); } - return null; } /** @@ -166,24 +166,24 @@ public final class PrintManager { * @see PrintJob */ public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) { - PrintFileAdapter printable = new PrintFileAdapter(pdfFile); - return print(printJobName, printable, attributes); + FileDocumentAdapter documentAdapter = new FileDocumentAdapter(pdfFile); + return print(printJobName, documentAdapter, attributes); } /** - * Creates a print job for printing a {@link PrintAdapter} with default print + * Creates a print job for printing a {@link PrintDocumentAdapter} with default print * attributes. * * @param printJobName A name for the new print job. - * @param printAdapter The printable adapter to print. + * @param documentAdapter An adapter that emits the document to print. * @param attributes The default print job attributes. * @return The created print job. * * @see PrintJob */ - public PrintJob print(String printJobName, PrintAdapter printAdapter, + public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, PrintAttributes attributes) { - PrintAdapterDelegate delegate = new PrintAdapterDelegate(printAdapter, + PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter, mContext.getMainLooper()); try { PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate, @@ -217,145 +217,118 @@ public final class PrintManager { } } - private static final class PrintAdapterDelegate extends IPrintAdapter.Stub { - private final Object mLock = new Object(); - - private PrintAdapter mPrintAdapter; + private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub { + private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish() - private Handler mHandler; + private Handler mHandler; // Strong reference OK - cleared in finish() - public PrintAdapterDelegate(PrintAdapter printAdapter, Looper looper) { - mPrintAdapter = printAdapter; + public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) { + mDocumentAdapter = documentAdapter; mHandler = new MyHandler(looper); } @Override public void start() { - synchronized (mLock) { - if (isFinishedLocked()) { - return; - } - mHandler.obtainMessage(MyHandler.MESSAGE_START, - mPrintAdapter).sendToTarget(); - } + mHandler.sendEmptyMessage(MyHandler.MSG_START); } @Override - public void printAttributesChanged(PrintAttributes attributes) { - synchronized (mLock) { - if (isFinishedLocked()) { - return; - } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = mPrintAdapter; - args.arg2 = attributes; - mHandler.obtainMessage(MyHandler.MESSAGE_PRINT_ATTRIBUTES_CHANGED, - args).sendToTarget(); - } + public void layout(PrintAttributes oldAttributes, + PrintAttributes newAttributes, ILayoutResultCallback callback) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = oldAttributes; + args.arg2 = newAttributes; + args.arg3 = callback; + mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget(); } @Override - public void print(List<PageRange> pages, ParcelFileDescriptor fd, - IPrintResultCallback callback) { - synchronized (mLock) { - if (isFinishedLocked()) { - return; - } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = mPrintAdapter; - args.arg2 = pages; - args.arg3 = fd.getFileDescriptor(); - args.arg4 = callback; - mHandler.obtainMessage(MyHandler.MESSAGE_PRINT, args).sendToTarget(); - } + public void write(List<PageRange> pages, ParcelFileDescriptor fd, + IWriteResultCallback callback) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = pages; + args.arg2 = fd.getFileDescriptor(); + args.arg3 = callback; + mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget(); } @Override public void finish() { - synchronized (mLock) { - if (isFinishedLocked()) { - return; - } - mHandler.obtainMessage(MyHandler.MESSAGE_FINIS, - mPrintAdapter).sendToTarget(); - } + mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); } - private boolean isFinishedLocked() { - return mPrintAdapter == null; + private boolean isFinished() { + return mDocumentAdapter == null; } - private void finishLocked() { - mPrintAdapter = null; + private void doFinish() { + mDocumentAdapter = null; mHandler = null; } private final class MyHandler extends Handler { - public static final int MESSAGE_START = 1; - public static final int MESSAGE_PRINT_ATTRIBUTES_CHANGED = 2; - public static final int MESSAGE_PRINT = 3; - public static final int MESSAGE_FINIS = 4; + public static final int MSG_START = 1; + public static final int MSG_LAYOUT = 2; + public static final int MSG_WRITE = 3; + public static final int MSG_FINISH = 4; public MyHandler(Looper looper) { super(looper, null, true); } @Override + @SuppressWarnings("unchecked") public void handleMessage(Message message) { + if (isFinished()) { + return; + } switch (message.what) { - case MESSAGE_START: { - PrintAdapter adapter = (PrintAdapter) message.obj; - adapter.onStart(); + case MSG_START: { + mDocumentAdapter.onStart(); } break; - case MESSAGE_PRINT_ATTRIBUTES_CHANGED: { + case MSG_LAYOUT: { SomeArgs args = (SomeArgs) message.obj; - PrintAdapter adapter = (PrintAdapter) args.arg1; - PrintAttributes attributes = (PrintAttributes) args.arg2; + PrintAttributes oldAttributes = (PrintAttributes) args.arg1; + PrintAttributes newAttributes = (PrintAttributes) args.arg2; + ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3; args.recycle(); - adapter.onPrintAttributesChanged(attributes); + + try { + ICancellationSignal remoteSignal = CancellationSignal.createTransport(); + callback.onLayoutStarted(remoteSignal); + + mDocumentAdapter.onLayout(oldAttributes, newAttributes, + CancellationSignal.fromTransport(remoteSignal), + new LayoutResultCallbackWrapper(callback)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error printing", re); + } } break; - case MESSAGE_PRINT: { + case MSG_WRITE: { SomeArgs args = (SomeArgs) message.obj; - PrintAdapter adapter = (PrintAdapter) args.arg1; - @SuppressWarnings("unchecked") - List<PageRange> pages = (List<PageRange>) args.arg2; - final FileDescriptor fd = (FileDescriptor) args.arg3; - IPrintResultCallback callback = (IPrintResultCallback) args.arg4; + List<PageRange> pages = (List<PageRange>) args.arg1; + FileDescriptor fd = (FileDescriptor) args.arg2; + IWriteResultCallback callback = (IWriteResultCallback) args.arg3; args.recycle(); + try { ICancellationSignal remoteSignal = CancellationSignal.createTransport(); - callback.onPrintStarted(adapter.getInfo(), remoteSignal); - - CancellationSignal localSignal = CancellationSignal.fromTransport( - remoteSignal); - adapter.onPrint(pages, fd, localSignal, - new PrintResultCallbackWrapper(callback) { - @Override - public void onPrintFinished(List<PageRange> pages) { - IoUtils.closeQuietly(fd); - super.onPrintFinished(pages); - } - - @Override - public void onPrintFailed(CharSequence error) { - IoUtils.closeQuietly(fd); - super.onPrintFailed(error); - } - }); + 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); } } break; - case MESSAGE_FINIS: { - PrintAdapter adapter = (PrintAdapter) message.obj; - adapter.onFinish(); - synchronized (mLock) { - finishLocked(); - } + case MSG_FINISH: { + mDocumentAdapter.onFinish(); + doFinish(); } break; default: { @@ -367,29 +340,65 @@ public final class PrintManager { } } - private static abstract class PrintResultCallbackWrapper extends PrintResultCallback { + 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 IPrintResultCallback mWrappedCallback; + private final ILayoutResultCallback mWrappedCallback; - public PrintResultCallbackWrapper(IPrintResultCallback callback) { + public LayoutResultCallbackWrapper(ILayoutResultCallback callback) { mWrappedCallback = callback; } @Override - public void onPrintFinished(List<PageRange> pages) { + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { try { - mWrappedCallback.onPrintFinished(pages); + mWrappedCallback.onLayoutFinished(info, changed); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onPrintFinished", re); + Log.e(LOG_TAG, "Error calling onLayoutFinished", re); } } @Override - public void onPrintFailed(CharSequence error) { + public void onLayoutFailed(CharSequence error) { try { - mWrappedCallback.onPrintFailed(error); + mWrappedCallback.onLayoutFailed(error); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onPrintFailed", re); + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); } } } diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java index b853eb0f8e8a..8a3148c35c8a 100644 --- a/core/java/android/print/PrinterId.java +++ b/core/java/android/print/PrinterId.java @@ -54,7 +54,7 @@ public final class PrinterId implements Parcelable { * * @hide */ - public ComponentName getServiceComponentName() { + public ComponentName getService() { return mServiceComponentName; } diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java index 9283472ee5fc..da3b6bc27a16 100644 --- a/core/java/android/print/PrinterInfo.java +++ b/core/java/android/print/PrinterInfo.java @@ -86,6 +86,31 @@ public final class PrinterInfo implements Parcelable { mDefaults.put(PROPERTY_ORIENTATION, DEFAULT_UNDEFINED); } + private PrinterInfo(PrinterInfo prototype) { + mId = prototype.mId; + mLabel = prototype.mLabel; + mStatus = prototype.mStatus; + + mMinMargins = prototype.mMinMargins; + mMediaSizes.addAll(prototype.mMediaSizes); + mResolutions.addAll(prototype.mResolutions); + mInputTrays = (prototype.mInputTrays != null) + ? new ArrayList<Tray>(prototype.mInputTrays) : null; + mOutputTrays = (prototype.mOutputTrays != null) + ? new ArrayList<Tray>(prototype.mOutputTrays) : null; + + mDuplexModes = prototype.mDuplexModes; + mColorModes = prototype.mColorModes; + mFittingModes = prototype.mFittingModes; + mOrientations = prototype.mOrientations; + + final int defaultCount = prototype.mDefaults.size(); + for (int i = 0; i < defaultCount; i++) { + mDefaults.put(prototype.mDefaults.keyAt(i), prototype.mDefaults.valueAt(i)); + } + mDefaultMargins = prototype.mDefaultMargins; + } + /** * Get the globally unique printer id. * @@ -437,7 +462,7 @@ public final class PrinterInfo implements Parcelable { * </p> */ public static final class Builder { - private final PrinterInfo mPrinterInfo; + private final PrinterInfo mPrototype; /** * Creates a new instance. @@ -455,9 +480,9 @@ public final class PrinterInfo implements Parcelable { if (TextUtils.isEmpty(label)) { throw new IllegalArgumentException("label cannot be empty."); } - mPrinterInfo = new PrinterInfo(); - mPrinterInfo.mLabel = label; - mPrinterInfo.mId = printerId; + mPrototype = new PrinterInfo(); + mPrototype.mLabel = label; + mPrototype.mId = printerId; } /** @@ -470,7 +495,7 @@ public final class PrinterInfo implements Parcelable { * @return This builder. */ public Builder setStatus(int status) { - mPrinterInfo.mStatus = status; + mPrototype.mStatus = status; return this; } @@ -489,11 +514,11 @@ public final class PrinterInfo implements Parcelable { * @see PrintAttributes.MediaSize */ public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) { - final int insertionIndex = mPrinterInfo.mMediaSizes.size(); - mPrinterInfo.mMediaSizes.add(mediaSize); + final int insertionIndex = mPrototype.mMediaSizes.size(); + mPrototype.mMediaSizes.add(mediaSize); if (isDefault) { throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE); - mPrinterInfo.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex); + mPrototype.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex); } return this; } @@ -514,11 +539,11 @@ public final class PrinterInfo implements Parcelable { * @see PrintAttributes.Resolution */ public Builder addResolution(Resolution resolution, boolean isDefault) { - final int insertionIndex = mPrinterInfo.mResolutions.size(); - mPrinterInfo.mResolutions.add(resolution); + final int insertionIndex = mPrototype.mResolutions.size(); + mPrototype.mResolutions.add(resolution); if (isDefault) { throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION); - mPrinterInfo.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex); + mPrototype.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex); } return this; } @@ -543,8 +568,8 @@ public final class PrinterInfo implements Parcelable { throw new IllegalArgumentException("Default margins" + " cannot be outside of the min margins."); } - mPrinterInfo.mMinMargins = margins; - mPrinterInfo.mDefaultMargins = defaultMargins; + mPrototype.mMinMargins = margins; + mPrototype.mDefaultMargins = defaultMargins; return this; } @@ -564,14 +589,14 @@ public final class PrinterInfo implements Parcelable { * @see PrintAttributes.Tray */ public Builder addInputTray(Tray inputTray, boolean isDefault) { - if (mPrinterInfo.mInputTrays == null) { - mPrinterInfo.mInputTrays = new ArrayList<Tray>(); + if (mPrototype.mInputTrays == null) { + mPrototype.mInputTrays = new ArrayList<Tray>(); } - final int insertionIndex = mPrinterInfo.mInputTrays.size(); - mPrinterInfo.mInputTrays.add(inputTray); + final int insertionIndex = mPrototype.mInputTrays.size(); + mPrototype.mInputTrays.add(inputTray); if (isDefault) { throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY); - mPrinterInfo.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex); + mPrototype.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex); } return this; } @@ -592,14 +617,14 @@ public final class PrinterInfo implements Parcelable { * @see PrintAttributes.Tray */ public Builder addOutputTray(Tray outputTray, boolean isDefault) { - if (mPrinterInfo.mOutputTrays == null) { - mPrinterInfo.mOutputTrays = new ArrayList<Tray>(); + if (mPrototype.mOutputTrays == null) { + mPrototype.mOutputTrays = new ArrayList<Tray>(); } - final int insertionIndex = mPrinterInfo.mOutputTrays.size(); - mPrinterInfo.mOutputTrays.add(outputTray); + final int insertionIndex = mPrototype.mOutputTrays.size(); + mPrototype.mOutputTrays.add(outputTray); if (isDefault) { throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY); - mPrinterInfo.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex); + mPrototype.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex); } return this; } @@ -631,8 +656,8 @@ public final class PrinterInfo implements Parcelable { throw new IllegalArgumentException("Default color mode not in color modes."); } PrintAttributes.enforceValidColorMode(colorModes); - mPrinterInfo.mColorModes = colorModes; - mPrinterInfo.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode); + mPrototype.mColorModes = colorModes; + mPrototype.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode); return this; } @@ -664,8 +689,8 @@ public final class PrinterInfo implements Parcelable { throw new IllegalArgumentException("Default duplex mode not in duplex modes."); } PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); - mPrinterInfo.mDuplexModes = duplexModes; - mPrinterInfo.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode); + mPrototype.mDuplexModes = duplexModes; + mPrototype.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode); return this; } @@ -696,8 +721,8 @@ public final class PrinterInfo implements Parcelable { throw new IllegalArgumentException("Default fitting mode not in fiting modes."); } PrintAttributes.enfoceValidFittingMode(defaultFittingMode); - mPrinterInfo.mFittingModes = fittingModes; - mPrinterInfo.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode); + mPrototype.mFittingModes = fittingModes; + mPrototype.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode); return this; } @@ -728,8 +753,8 @@ public final class PrinterInfo implements Parcelable { throw new IllegalArgumentException("Default orientation not in orientations."); } PrintAttributes.enforceValidOrientation(defaultOrientation); - mPrinterInfo.mOrientations = orientations; - mPrinterInfo.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation); + mPrototype.mOrientations = orientations; + mPrototype.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation); return this; } @@ -743,41 +768,41 @@ public final class PrinterInfo implements Parcelable { * @throws IllegalStateException If a required attribute was not specified. */ public PrinterInfo create() { - if (mPrinterInfo.mMediaSizes == null || mPrinterInfo.mMediaSizes.isEmpty()) { + if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) { throw new IllegalStateException("No media size specified."); } - if (mPrinterInfo.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) { + if (mPrototype.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) { throw new IllegalStateException("No default media size specified."); } - if (mPrinterInfo.mResolutions == null || mPrinterInfo.mResolutions.isEmpty()) { + if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) { throw new IllegalStateException("No resolution specified."); } - if (mPrinterInfo.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) { + if (mPrototype.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) { throw new IllegalStateException("No default resolution specified."); } - if (mPrinterInfo.mColorModes == 0) { + if (mPrototype.mColorModes == 0) { throw new IllegalStateException("No color mode specified."); } - if (mPrinterInfo.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) { + if (mPrototype.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) { throw new IllegalStateException("No default color mode specified."); } - if (mPrinterInfo.mOrientations == 0) { + if (mPrototype.mOrientations == 0) { throw new IllegalStateException("No oprientation specified."); } - if (mPrinterInfo.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) { + if (mPrototype.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) { throw new IllegalStateException("No default orientation specified."); } - if (mPrinterInfo.mMinMargins == null) { - mPrinterInfo.mMinMargins = new Margins(0, 0, 0, 0); + if (mPrototype.mMinMargins == null) { + mPrototype.mMinMargins = new Margins(0, 0, 0, 0); } - if (mPrinterInfo.mDefaultMargins == null) { - mPrinterInfo.mDefaultMargins = mPrinterInfo.mMinMargins; + if (mPrototype.mDefaultMargins == null) { + mPrototype.mDefaultMargins = mPrototype.mMinMargins; } - return mPrinterInfo; + return new PrinterInfo(mPrototype); } private void throwIfDefaultAlreadySpecified(int propertyIndex) { - if (mPrinterInfo.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) { + if (mPrototype.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) { throw new IllegalArgumentException("Default already specified."); } } diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl index eabd96dc0118..c72385adda3f 100644 --- a/core/java/android/printservice/IPrintService.aidl +++ b/core/java/android/printservice/IPrintService.aidl @@ -17,6 +17,7 @@ package android.printservice; import android.os.ICancellationSignal; +import android.print.IPrinterDiscoveryObserver; import android.print.PrintJobInfo; import android.print.PrinterId; import android.printservice.IPrintServiceClient; @@ -28,8 +29,8 @@ import android.printservice.IPrintServiceClient; */ oneway interface IPrintService { void setClient(IPrintServiceClient client); - void requestCancelPrintJob(in PrintJobInfo printJob); - void onPrintJobQueued(in PrintJobInfo printJob); - void startPrinterDiscovery(); + void requestCancelPrintJob(in PrintJobInfo printJobInfo); + void onPrintJobQueued(in PrintJobInfo printJobInfo); + void startPrinterDiscovery(IPrinterDiscoveryObserver observer); void stopPrinterDiscovery(); } diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl index cff8c028b93b..cdde4d865162 100644 --- a/core/java/android/printservice/IPrintServiceClient.aidl +++ b/core/java/android/printservice/IPrintServiceClient.aidl @@ -27,11 +27,9 @@ import android.print.PrinterInfo; * @hide */ interface IPrintServiceClient { - List<PrintJobInfo> getPrintJobs(); - PrintJobInfo getPrintJob(int printJobId); + List<PrintJobInfo> getPrintJobInfos(); + PrintJobInfo getPrintJobInfo(int printJobId); boolean setPrintJobState(int printJobId, int status); boolean setPrintJobTag(int printJobId, String tag); oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); - oneway void addDiscoveredPrinters(in List<PrinterInfo> printers); - oneway void removeDiscoveredPrinters(in List<PrinterId> printers); } diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java new file mode 100644 index 000000000000..2a1581a602d5 --- /dev/null +++ b/core/java/android/printservice/PrintDocument.java @@ -0,0 +1,91 @@ +/* + * 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.printservice; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.PrintDocumentInfo; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * This class represents a printed document from the perspective of a print + * service. It exposes APIs to query the document and obtain its data. + */ +public final class PrintDocument { + + private static final String LOG_TAG = "PrintDocument"; + + private final int mPrintJobId; + + private final IPrintServiceClient mPrintServiceClient; + + private final PrintDocumentInfo mInfo; + + PrintDocument(int printJobId, IPrintServiceClient printServiceClient, + PrintDocumentInfo info) { + mPrintJobId = printJobId; + mPrintServiceClient = printServiceClient; + mInfo = info; + } + + /** + * Gets the {@link PrintDocumentInfo} that describes this document. + * + * @return The document info. + */ + public PrintDocumentInfo getInfo() { + return mInfo; + } + + /** + * Gets the data associated with this document. It is a responsibility of the + * client to open a stream to the returned file descriptor and fully read the + * data. + * <p> + * <strong>Note:</strong> It is your responsibility to close the file descriptor. + * </p> + * + * @return A file descriptor for reading the data or <code>null</code>. + */ + public FileDescriptor getData() { + ParcelFileDescriptor source = null; + ParcelFileDescriptor sink = null; + try { + ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); + source = fds[0]; + sink = fds[1]; + mPrintServiceClient.writePrintJobData(sink, mPrintJobId); + return source.getFileDescriptor(); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error calling getting print job data!", ioe); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getting print job data!", re); + } finally { + if (sink != null) { + try { + sink.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + return null; + } +} diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java index f490f911936f..80530a7cb8d6 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -16,10 +16,6 @@ package android.printservice; -import java.io.FileDescriptor; -import java.io.IOException; - -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.print.PrintJobInfo; import android.util.Log; @@ -33,19 +29,16 @@ public final class PrintJob { private static final String LOG_TAG = "PrintJob"; - private final int mId; - private final IPrintServiceClient mPrintServiceClient; + private final PrintDocument mDocument; + private PrintJobInfo mCachedInfo; - PrintJob(PrintJobInfo info, IPrintServiceClient client) { - if (client == null) { - throw new IllegalStateException("Print serivice not connected!"); - } - mCachedInfo = info; - mId = info.getId(); + PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) { + mCachedInfo = jobInfo; mPrintServiceClient = client; + mDocument = new PrintDocument(mCachedInfo.getId(), client, jobInfo.getDocumentInfo()); } /** @@ -54,7 +47,7 @@ public final class PrintJob { * @return The id. */ public int getId() { - return mId; + return mCachedInfo.getId(); } /** @@ -70,9 +63,9 @@ public final class PrintJob { public PrintJobInfo getInfo() { PrintJobInfo info = null; try { - info = mPrintServiceClient.getPrintJob(mId); + info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId()); } catch (RemoteException re) { - Log.e(LOG_TAG, "Couldn't get info for job: " + mId, re); + Log.e(LOG_TAG, "Couldn't get info for job: " + mCachedInfo.getId(), re); } if (info != null) { mCachedInfo = info; @@ -81,6 +74,15 @@ public final class PrintJob { } /** + * Gets the document of this print job. + * + * @return The document. + */ + public PrintDocument getDocument() { + return mDocument; + } + + /** * Gets whether this print job is queued. Such a print job is * ready to be printed and can be started. * @@ -103,7 +105,7 @@ public final class PrintJob { * @see #fail(CharSequence) */ public boolean isStarted() { - return getInfo().getState() == PrintJobInfo.STATE_STARTED; + return getInfo().getState() == PrintJobInfo.STATE_STARTED; } /** @@ -181,48 +183,13 @@ public final class PrintJob { */ public boolean setTag(String tag) { try { - return mPrintServiceClient.setPrintJobTag(mId, tag); + return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error setting tag for job:" + mId, re); + Log.e(LOG_TAG, "Error setting tag for job: " + mCachedInfo.getId(), re); } return false; } - /** - * Gets the data associated with this print job. It is a responsibility of - * the print service to open a stream to the returned file descriptor - * and fully read the content. - * <p> - * <strong>Note:</strong> It is your responsibility to close the file descriptor. - * </p> - * - * @return A file descriptor for reading the data or <code>null</code>. - */ - public final FileDescriptor getData() { - ParcelFileDescriptor source = null; - ParcelFileDescriptor sink = null; - try { - ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); - source = fds[0]; - sink = fds[1]; - mPrintServiceClient.writePrintJobData(sink, mId); - return source.getFileDescriptor(); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error calling getting print job data!", ioe); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling getting print job data!", re); - } finally { - if (sink != null) { - try { - sink.close(); - } catch (IOException ioe) { - /* ignore */ - } - } - } - return null; - } - @Override public boolean equals(Object obj) { if (this == obj) { @@ -235,23 +202,25 @@ public final class PrintJob { return false; } PrintJob other = (PrintJob) obj; - return (mId == other.mId); + return (mCachedInfo.getId() == other.mCachedInfo.getId()); } @Override public int hashCode() { - return mId; + return mCachedInfo.getId(); } private boolean setState(int state) { - // Best effort - update the state of the cached info since - // we may not be able to re-fetch it later if the job gets - // removed from the spooler. - mCachedInfo.setState(state); try { - return mPrintServiceClient.setPrintJobState(mId, state); + if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) { + // Best effort - update the state of the cached info since + // we may not be able to re-fetch it later if the job gets + // removed from the spooler as a result of the state change. + mCachedInfo.setState(state); + return true; + } } catch (RemoteException re) { - Log.e(LOG_TAG, "Error setting the state of job:" + mId, re); + Log.e(LOG_TAG, "Error setting the state of job: " + mCachedInfo.getId(), re); } return false; } diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index 92569661138d..820c2d8ff4ef 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.print.IPrinterDiscoveryObserver; import android.print.PrintJobInfo; import android.print.PrinterId; import android.print.PrinterInfo; @@ -47,7 +48,7 @@ import java.util.List; * {@link #onStartPrinterDiscovery()} and ends with a call to * {@link #onStopPrinterDiscovery()}. During a printer discovery * period the print service reports newly discovered printers by - * calling {@link #addDiscoveredPrinters(List)} and added printers + * calling {@link #addDiscoveredPrinters(List)} and reports added printers * that disappeared by calling {@link #removeDiscoveredPrinters(List)}. * Calls to {@link #addDiscoveredPrinters(List)} and * {@link #removeDiscoveredPrinters(List)} before a call to @@ -67,26 +68,30 @@ import java.util.List; * a call to {@link #onPrintJobQueued(PrintJob)} is made and the print * service may handle it immediately or schedule that for an appropriate * time in the future. The list of all print jobs for this service - * are be available by calling {@link #getPrintJobs()}. A queued print - * job is one whose {@link PrintJob#isQueued()} return true. + * are be available by calling {@link #getPrintJobs()}. * </p> * <p> * A print service is responsible for setting the print job state as * appropriate while processing it. Initially, a print job is in a * {@link PrintJobInfo#STATE_QUEUED} state which means that the data to * be printed is spooled by the system and the print service can obtain - * that data by calling {@link PrintJob#getData()}. After the print - * service starts printing the data it should set the print job state - * to {@link PrintJobInfo#STATE_STARTED}. Upon successful completion, the - * print job state has to be set to {@link PrintJobInfo#STATE_COMPLETED}. - * In a case of a failure, the print job state should be set to - * {@link PrintJobInfo#STATE_FAILED}. If a print job is in a - * {@link PrintJobInfo#STATE_STARTED} state and the user requests to - * cancel it, the print service will receive a call to - * {@link #onRequestCancelPrintJob(PrintJob)} which requests from the - * service to do a best effort in canceling the job. In case the job - * is successfully canceled, its state has to be set to - * {@link PrintJobInfo#STATE_CANCELED}. + * that data by calling {@link PrintJob#getDocument()}. A queued print + * job's {@link PrintJob#isQueued()} method returns true. + * </p> + * <p> + * After the print service starts printing the data it should set the + * print job state to {@link PrintJobInfo#STATE_STARTED} by calling + * {@link PrintJob#start()}. Upon successful completion, the print job + * state has to be set to {@link PrintJobInfo#STATE_COMPLETED} by calling + * {@link PrintJob#complete()}. In case of a failure, the print job + * state should be set to {@link PrintJobInfo#STATE_FAILED} by calling + * {@link PrintJob#fail(CharSequence)}. If a print job is in a + * {@link PrintJobInfo#STATE_STARTED} state, i.e. {@link PrintJob#isStarted()} + * return true, and the user requests to cancel it, the print service will + * receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which + * requests from the service to do a best effort in canceling the job. In + * case the job is successfully canceled, its state has to be set to + * {@link PrintJobInfo#STATE_CANCELED}. by calling {@link PrintJob#cancel()}. * </p> * <h3>Lifecycle</h3> * <p> @@ -124,9 +129,9 @@ import java.util.List; * <p> * A print service can be configured by specifying an optional settings * activity which exposes service specific options, an optional add - * prints activity which is used for manual addition of printers, etc. - * It is a responsibility of the system to launch the settings and add - * printers activities when appropriate. + * prints activity which is used for manual addition of printers, vendor + * name ,etc. It is a responsibility of the system to launch the settings + * and add printers activities when appropriate. * </p> * <p> * A print service is configured by providing a @@ -148,7 +153,7 @@ import java.util.List; */ public abstract class PrintService extends Service { - private static final String LOG_TAG = PrintService.class.getSimpleName(); + private static final String LOG_TAG = "PrintService"; /** * The {@link Intent} action that must be declared as handled by a service @@ -162,6 +167,7 @@ public abstract class PrintService extends Service { * <code><{@link android.R.styleable#PrintService print-service}></code> * tag. This is a a sample XML file configuring a print service: * <pre> <print-service + * android:vendor="SomeVendor" * android:settingsActivity="foo.bar.MySettingsActivity" * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." * . . . @@ -175,7 +181,7 @@ public abstract class PrintService extends Service { private IPrintServiceClient mClient; - private boolean mDiscoveringPrinters; + private IPrinterDiscoveryObserver mDiscoveryObserver; @Override protected void attachBaseContext(Context base) { @@ -230,29 +236,29 @@ public abstract class PrintService extends Service { * printers have to be added. You can call this method as many times as * necessary during the discovery period but should not pass in already * added printers. If a printer is already added in the same printer - * discovery period, it will be ignored. + * discovery period, it will be ignored. If you want to update an already + * added printer, you should removed it and then re-add it. * </p> * * @param printers A list with discovered printers. * - * @throws IllegalStateException If this service is not connected. - * * @see #removeDiscoveredPrinters(List) * @see #onStartPrinterDiscovery() * @see #onStopPrinterDiscovery() + * + * @throws IllegalStateException If this service is not connected. */ public final void addDiscoveredPrinters(List<PrinterInfo> printers) { + final IPrinterDiscoveryObserver observer; synchronized (mLock) { - if (mClient == null) { - throw new IllegalStateException("Print serivice not connected!"); - } - if (mDiscoveringPrinters) { - try { - // Calling with a lock into the system is fine. - mClient.addDiscoveredPrinters(printers); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error adding discovered printers!", re); - } + throwIfNotConnectedLocked(); + observer = mDiscoveryObserver; + } + if (observer != null) { + try { + observer.addDiscoveredPrinters(printers); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error adding discovered printers", re); } } } @@ -269,37 +275,38 @@ public abstract class PrintService extends Service { * this method as many times as necessary during the discovery period * but should not pass in already removed printer ids. If a printer with * a given id is already removed in the same discovery period, it will - * be ignored. + * be ignored. If you want to update an already added printer, you should + * removed it and then re-add it. * </p> * * @param printerIds A list with disappeared printer ids. * - * @throws IllegalStateException If this service is not connected. - * * @see #addDiscoveredPrinters(List) * @see #onStartPrinterDiscovery() * @see #onStopPrinterDiscovery() + * + * @throws IllegalStateException If this service is not connected. */ public final void removeDiscoveredPrinters(List<PrinterId> printerIds) { + final IPrinterDiscoveryObserver observer; synchronized (mLock) { - if (mClient == null) { - throw new IllegalStateException("Print serivice not connected!"); - } - if (mDiscoveringPrinters) { - try { - // Calling with a lock into the system is fine. - mClient.removeDiscoveredPrinters(printerIds); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error removing discovered printers!", re); - } + throwIfNotConnectedLocked(); + observer = mDiscoveryObserver; + } + if (observer != null) { + try { + observer.removeDiscoveredPrinters(printerIds); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error removing discovered printers", re); } } } /** * Called when canceling of a print job is requested. The service - * should do best effort to fulfill the request. After the print - * job is canceled by calling {@link PrintJob#cancel()}. + * should do best effort to fulfill the request. After the cancellation + * is performed, the print job should be set to a cancelled state by + * calling {@link PrintJob#cancel()}. * * @param printJob The print job to be canceled. */ @@ -310,11 +317,12 @@ public abstract class PrintService extends Service { * Called when there is a queued print job for one of the printers * managed by this print service. A queued print job is ready for * processing by a print service which can get the data to be printed - * by calling {@link PrintJob#getData()}. This service may start + * by calling {@link PrintJob#getDocument()}. This service may start * processing the passed in print job or schedule handling of queued * print jobs at a convenient time. The service can get the print * jobs by a call to {@link #getPrintJobs()} and examine their state - * to find the ones with state {@link PrintJobInfo#STATE_QUEUED}. + * to find the ones with state {@link PrintJobInfo#STATE_QUEUED} by + * calling {@link PrintJob#isQueued()}. * * @param printJob The new queued print job. * @@ -330,28 +338,31 @@ public abstract class PrintService extends Service { * @throws IllegalStateException If this service is not connected. */ public final List<PrintJob> getPrintJobs() { + final IPrintServiceClient client; synchronized (mLock) { - if (mClient == null) { - throw new IllegalStateException("Print serivice not connected!"); - } - try { - List<PrintJob> printJobs = null; - List<PrintJobInfo> printJobInfos = mClient.getPrintJobs(); - if (printJobInfos != null) { - final int printJobInfoCount = printJobInfos.size(); - printJobs = new ArrayList<PrintJob>(printJobInfoCount); - for (int i = 0; i < printJobInfoCount; i++) { - printJobs.add(new PrintJob(printJobInfos.get(i), mClient)); - } - } - if (printJobs != null) { - return printJobs; + throwIfNotConnectedLocked(); + client = mClient; + } + if (client == null) { + return Collections.emptyList(); + } + try { + List<PrintJob> printJobs = null; + List<PrintJobInfo> printJobInfos = client.getPrintJobInfos(); + if (printJobInfos != null) { + final int printJobInfoCount = printJobInfos.size(); + printJobs = new ArrayList<PrintJob>(printJobInfoCount); + for (int i = 0; i < printJobInfoCount; i++) { + printJobs.add(new PrintJob(printJobInfos.get(i), client)); } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling getPrintJobs()", re); } - return Collections.emptyList(); + if (printJobs != null) { + return printJobs; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling getPrintJobs()", re); } + return Collections.emptyList(); } /** @@ -375,8 +386,9 @@ public abstract class PrintService extends Service { } @Override - public void startPrinterDiscovery() { - mHandler.sendEmptyMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY); + public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) { + mHandler.obtainMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY, + observer).sendToTarget(); } @Override @@ -385,18 +397,25 @@ public abstract class PrintService extends Service { } @Override - public void requestCancelPrintJob(PrintJobInfo printJob) { - mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB, printJob).sendToTarget(); + public void requestCancelPrintJob(PrintJobInfo printJobInfo) { + mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB, + printJobInfo).sendToTarget(); } @Override - public void onPrintJobQueued(PrintJobInfo printJob) { + public void onPrintJobQueued(PrintJobInfo printJobInfo) { mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED, - printJob).sendToTarget(); + printJobInfo).sendToTarget(); } }; } + private void throwIfNotConnectedLocked() { + if (mClient == null) { + throw new IllegalStateException("Print serivice not connected"); + } + } + private final class MyHandler extends Handler { public static final int MESSAGE_START_PRINTER_DISCOVERY = 1; public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2; @@ -414,26 +433,26 @@ public abstract class PrintService extends Service { switch (action) { case MESSAGE_START_PRINTER_DISCOVERY: { synchronized (mLock) { - mDiscoveringPrinters = true; + mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj; } onStartPrinterDiscovery(); } break; case MESSAGE_STOP_PRINTER_DISCOVERY: { synchronized (mLock) { - mDiscoveringPrinters = false; + mDiscoveryObserver = null; } onStopPrinterDiscovery(); } break; case MESSAGE_CANCEL_PRINTJOB: { - PrintJobInfo printJob = (PrintJobInfo) message.obj; - onRequestCancelPrintJob(new PrintJob(printJob, mClient)); + PrintJobInfo printJobInfo = (PrintJobInfo) message.obj; + onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient)); } break; case MESSAGE_ON_PRINTJOB_QUEUED: { - PrintJobInfo printJob = (PrintJobInfo) message.obj; - onPrintJobQueued(new PrintJob(printJob, mClient)); + PrintJobInfo printJobInfo = (PrintJobInfo) message.obj; + onPrintJobQueued(new PrintJob(printJobInfo, mClient)); } break; case MESSAGE_SET_CLEINT: { @@ -441,13 +460,12 @@ public abstract class PrintService extends Service { synchronized (mLock) { mClient = client; if (client == null) { - mDiscoveringPrinters = false; + mDiscoveryObserver = null; } } if (client != null) { onConnected(); } else { - onStopPrinterDiscovery(); onDisconnected(); } } break; diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java index 0370a252531b..43dd1b67de66 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -48,8 +48,6 @@ import java.io.IOException; */ public final class PrintServiceInfo implements Parcelable { - private static final boolean DEBUG = false; - private static final String LOG_TAG = PrintServiceInfo.class.getSimpleName(); private static final String TAG_PRINT_SERVICE = "print-service"; @@ -97,7 +95,6 @@ public final class PrintServiceInfo implements Parcelable { * @param context Context for accessing resources. * @throws XmlPullParserException If a XML parsing error occurs. * @throws IOException If a I/O error occurs. - * @hide */ public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) { String settingsActivityName = null; @@ -117,7 +114,7 @@ public final class PrintServiceInfo implements Parcelable { String nodeName = parser.getName(); if (!TAG_PRINT_SERVICE.equals(nodeName)) { throw new XmlPullParserException( - "Meta-data does not start with" + TAG_PRINT_SERVICE + " tag"); + "Meta-data does not start with " + TAG_PRINT_SERVICE + " tag"); } Resources resources = packageManager.getResourcesForApplication( @@ -213,7 +210,7 @@ public final class PrintServiceInfo implements Parcelable { @Override public int hashCode() { - return 31 * 1 + ((mId == null) ? 0 : mId.hashCode()); + return 31 + ((mId == null) ? 0 : mId.hashCode()); } @Override @@ -244,12 +241,8 @@ public final class PrintServiceInfo implements Parcelable { builder.append("PrintServiceInfo{"); builder.append("id:").append(mId).append(", "); builder.append("resolveInfo:").append(mResolveInfo).append(", "); - if (DEBUG) { - builder.append("settingsActivityName:").append(mSettingsActivityName); - builder.append("addPrintersActivityName:").append(mAddPrintersActivityName); - } else if (mSettingsActivityName != null || mAddPrintersActivityName != null) { - builder.append("<has meta-data>"); - } + builder.append("settingsActivityName:").append(mSettingsActivityName); + builder.append("addPrintersActivityName:").append(mAddPrintersActivityName); builder.append("}"); return builder.toString(); } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 37c5d48ee28c..900f28a0ddec 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4169,86 +4169,86 @@ <!-- Printing --> - <!-- ISO A0 media size: 33.11" × 46.81" --> + <!-- ISO (European standard) A0 media (paper) size: 33.11" × 46.81" --> <string name="mediaSize_iso_a0">ISO A0</string> - <!-- ISO A1 media size: 23.39" × 33.11" --> + <!-- ISO (European standard) A1 media (paper) size: 23.39" × 33.11" --> <string name="mediaSize_iso_a1">ISO A1</string> - <!-- ISO A2 media size: 16.54" x 23.39" --> + <!-- ISO (European standard) A2 media (paper) size: 16.54" x 23.39" --> <string name="mediaSize_iso_a2">ISO A2</string> - <!-- ISO A3 media size: 11.69" x 16.54" --> + <!-- ISO (European standard) A3 media (paper) size: 11.69" x 16.54" --> <string name="mediaSize_iso_a3">ISO A3</string> - <!-- ISO A4 media size: 8.27" x 11.69" --> + <!-- ISO (European standard) A4 media (paper) size: 8.27" x 11.69" --> <string name="mediaSize_iso_a4">ISO A4</string> - <!-- ISO A5 media size: 5.83" x 8.27" --> + <!-- ISO (European standard) A5 media (paper) size: 5.83" x 8.27" --> <string name="mediaSize_iso_a5">ISO A5</string> - <!-- ISO A6 media size: 4.13" x 5.83" --> + <!-- ISO (European standard) A6 media (paper) size: 4.13" x 5.83" --> <string name="mediaSize_iso_a6">ISO A6</string> - <!-- ISO A7 media size: 2.91" x 4.13" --> + <!-- ISO (European standard) A7 media (paper) size: 2.91" x 4.13" --> <string name="mediaSize_iso_a7">ISO A7</string> - <!-- ISO A8 media size: 2.05" x 2.91" --> + <!-- ISO (European standard) A8 media (paper) size: 2.05" x 2.91" --> <string name="mediaSize_iso_a8">ISO A8</string> - <!-- ISO A9 media size: 1.46" x 2.05" --> + <!-- ISO (European standard) A9 media (paper) size: 1.46" x 2.05" --> <string name="mediaSize_iso_a9">ISO A9</string> - <!-- ISO A10 media size: 1.02" x 1.46" --> + <!-- ISO (European standard) A10 media (paper) size: 1.02" x 1.46" --> <string name="mediaSize_iso_a10">ISO A10</string> - <!-- ISO B0 media size: 39.37" x 55.67" --> + <!-- ISO (European standard) B0 media (paper) size: 39.37" x 55.67" --> <string name="mediaSize_iso_b0">ISO B0</string> - <!-- ISO B1 media size: 27.83" x 39.37" --> + <!-- ISO (European standard) B1 media (paper) size: 27.83" x 39.37" --> <string name="mediaSize_iso_b1">ISO B1</string> - <!-- ISO B2 media size - 19.69" x 27.83" --> + <!-- ISO (European standard) B2 media (paper) size - 19.69" x 27.83" --> <string name="mediaSize_iso_b2">ISO B2</string> - <!-- ISO B3 media size: 13.90" x 19.69" --> + <!-- ISO (European standard) B3 media (paper) size: 13.90" x 19.69" --> <string name="mediaSize_iso_b3">ISO B3</string> - <!-- ISO B4 media size: 9.84" x 13.90" --> + <!-- ISO (European standard) B4 media (paper) size: 9.84" x 13.90" --> <string name="mediaSize_iso_b4">ISO B4</string> - <!-- ISO B5 media size: 6.93" x 9.84" --> + <!-- ISO (European standard) B5 media (paper) size: 6.93" x 9.84" --> <string name="mediaSize_iso_b5">ISO B5</string> - <!-- ISO B6 media size: 4.92" x 6.93" --> + <!-- ISO (European standard) B6 media (paper) size: 4.92" x 6.93" --> <string name="mediaSize_iso_b6">ISO B6</string> - <!-- ISO B7 media size: 3.46" x 4.92" --> + <!-- ISO (European standard) B7 media (paper) size: 3.46" x 4.92" --> <string name="mediaSize_iso_b7">ISO B7</string> - <!-- ISO B8 media size: 2.44" x 3.46" --> + <!-- ISO (European standard) B8 media (paper) size: 2.44" x 3.46" --> <string name="mediaSize_iso_b8">ISO B8</string> - <!-- ISO B9 media size: 1.73" x 2.44" --> + <!-- ISO (European standard) B9 media (paper) size: 1.73" x 2.44" --> <string name="mediaSize_iso_b9">ISO B9</string> - <!-- ISO B10 media size: 1.22" x 1.73" --> + <!-- ISO (European standard) B10 media (paper) size: 1.22" x 1.73" --> <string name="mediaSize_iso_b10">ISO B10</string> - <!-- ISO C0 media size: 36.10" x 51.06" --> + <!-- ISO (European standard) C0 media (paper) size: 36.10" x 51.06" --> <string name="mediaSize_iso_c0">ISO C0</string> - <!-- ISO C1 media size: 25.51" x 36.10" --> + <!-- ISO (European standard) C1 media (paper) size: 25.51" x 36.10" --> <string name="mediaSize_iso_c1">ISO C1</string> - <!-- ISO C2 media size: 18.03" x 25.51" --> + <!-- ISO (European standard) C2 media (paper) size: 18.03" x 25.51" --> <string name="mediaSize_iso_c2">ISO C2</string> - <!-- ISO C3 media size: 12.76" x 18.03" --> + <!-- ISO (European standard) C3 media (paper) size: 12.76" x 18.03" --> <string name="mediaSize_iso_c3">ISO C3</string> - <!-- ISO C4 media size: 9.02" x 12.76" --> + <!-- ISO (European standard) C4 media (paper) size: 9.02" x 12.76" --> <string name="mediaSize_iso_c4">ISO C4</string> - <!-- ISO C5 media size: 6.38" x 9.02" --> + <!-- ISO (European standard) C5 media (paper) size: 6.38" x 9.02" --> <string name="mediaSize_iso_c5">ISO C5</string> - <!-- ISO C6 media size: 4.49" x 6.38" --> + <!-- ISO (European standard) C6 media (paper) size: 4.49" x 6.38" --> <string name="mediaSize_iso_c6">ISO C6</string> - <!-- ISO C7 media size: 3.19" x 4.49" --> + <!-- ISO (European standard) C7 media (paper) size: 3.19" x 4.49" --> <string name="mediaSize_iso_c7">ISO C7</string> - <!-- ISO ISO C8 media size: 2.24" x 3.19" --> + <!-- ISO ISO C8 media (paper) size: 2.24" x 3.19" --> <string name="mediaSize_iso_c8">ISO C8</string> - <!-- ISO ISO C9 media size: 1.57" x 2.24" --> + <!-- ISO ISO C9 media (paper) size: 1.57" x 2.24" --> <string name="mediaSize_iso_c9">ISO C9</string> - <!-- ISO C10 media size: 1.10" x 1.57" --> + <!-- ISO (European standard) C10 media (paper) size: 1.10" x 1.57" --> <string name="mediaSize_iso_c10">ISO C10</string> - <!-- North America Letter media size: 8.5" × 11" --> + <!-- North America Letter media (paper) size: 8.5" × 11" --> <string name="mediaSize_na_letter">Letter</string> - <!-- North America Government Letter media size: 8.0" × 10.5" --> + <!-- North America Government Letter media (paper) size: 8.0" × 10.5" --> <string name="mediaSize_na_gvrnmt_letter">Government Letter</string> - <!-- North America Legal media size: 8.5" × 14" --> + <!-- North America Legal media (paper) size: 8.5" × 14" --> <string name="mediaSize_na_legal">Legal</string> - <!-- North America Junior Legal media size: 8.0" × 5.0" --> + <!-- North America Junior Legal media (paper) size: 8.0" × 5.0" --> <string name="mediaSize_na_junior_legal">Junior Legal</string> - <!-- North America Ledger media size: 17" × 11" --> + <!-- North America Ledger media (paper) size: 17" × 11" --> <string name="mediaSize_na_ledger">Ledger</string> - <!-- North America Tabloid media size: 11" × 17" --> + <!-- North America Tabloid media (paper) size: 11" × 17" --> <string name="mediaSize_na_tabloid">Tabloid</string> <!-- PIN creation dialog message [CHAR LIMIT=none] --> diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index ae2fe5c43c52..7d07c055dbcb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -24,20 +24,21 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; -import android.os.IBinder.DeathRecipient; -import android.print.IPrintAdapter; -import android.print.IPrintManager; +import android.print.IPrintDocumentAdapter; import android.print.IPrinterDiscoveryObserver; import android.print.PageRange; import android.print.PrintAttributes; import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Resolution; import android.print.PrintAttributes.Tray; +import android.print.PrintDocumentAdapter.LayoutResultCallback; +import android.print.PrintDocumentAdapter.WriteResultCallback; +import android.print.PrintDocumentInfo; import android.print.PrintJobInfo; import android.print.PrinterId; import android.print.PrinterInfo; @@ -47,6 +48,7 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; +import android.view.Choreographer; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -58,7 +60,6 @@ import android.widget.EditText; import android.widget.Spinner; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -78,9 +79,7 @@ public class PrintJobConfigActivity extends Activity { private static final int MIN_COPIES = 1; - private final List<QueuedAsyncTask<?>> mTaskQueue = new ArrayList<QueuedAsyncTask<?>>(); - - private IPrintManager mPrintManager; + private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this); private IPrinterDiscoveryObserver mPrinterDiscoveryObserver; @@ -89,9 +88,7 @@ public class PrintJobConfigActivity extends Activity { private PrintAttributes mPrintAttributes; - private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this); - - private RemotePrintAdapter mRemotePrintAdapter; + private RemotePrintDocumentAdapter mRemotePrintAdapter; // UI elements @@ -124,11 +121,11 @@ public class PrintJobConfigActivity extends Activity { private Spinner mOrientationSpinner; public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter; - private boolean mPrintStarted; - private boolean mPrintConfirmed; - private IBinder mPrinable; + private boolean mStarted; + + private IBinder mIPrintDocumentAdapter; // TODO: Implement store/restore state. @@ -231,9 +228,6 @@ public class PrintJobConfigActivity extends Activity { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - mPrintManager = (IPrintManager) IPrintManager.Stub.asInterface( - ServiceManager.getService(PRINT_SERVICE)); - Bundle extras = getIntent().getExtras(); mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1); @@ -251,15 +245,16 @@ public class PrintJobConfigActivity extends Activity { mPrintAttributes = new PrintAttributes.Builder().create(); } - mPrinable = extras.getBinder(EXTRA_PRINTABLE); - if (mPrinable == null) { + mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE); + if (mIPrintDocumentAdapter == null) { throw new IllegalArgumentException("Printable cannot be null"); } - mRemotePrintAdapter = new RemotePrintAdapter(IPrintAdapter.Stub.asInterface(mPrinable), + mRemotePrintAdapter = new RemotePrintDocumentAdapter( + IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter), mPrintSpooler.generateFileForPrintJob(mPrintJobId)); try { - mPrinable.linkToDeath(mDeathRecipient, 0); + mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0); } catch (RemoteException re) { finish(); } @@ -271,7 +266,7 @@ public class PrintJobConfigActivity extends Activity { @Override protected void onDestroy() { - mPrinable.unlinkToDeath(mDeathRecipient, 0); + mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); super.onDestroy(); } @@ -367,7 +362,6 @@ public class PrintJobConfigActivity extends Activity { mPrintAttributes.getMediaSize()); mMediaSizeSpinner.setOnItemSelectedListener(null); mMediaSizeSpinner.setSelection(selectedMediaSizeIndex); - mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); // Resolution. mResolutionSpinnerAdapter.clear(); @@ -382,7 +376,19 @@ public class PrintJobConfigActivity extends Activity { mPrintAttributes.getResolution()); mResolutionSpinner.setOnItemSelectedListener(null); mResolutionSpinner.setSelection(selectedResolutionIndex); - mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + + // AdapterView has the weird behavior to notify the selection listener for a + // selection event that occurred *before* the listener was registered because + // it does the real selection change on the next layout pass. To avoid this + // behavior we re-attach the listener in the next traversal window - fun! + Choreographer.getInstance().postCallback( + Choreographer.CALLBACK_TRAVERSAL, new Runnable() { + @Override + public void run() { + mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + mResolutionSpinner.setOnItemSelectedListener(mOnItemSelectedListener); + } + }, null); // Input tray. mInputTraySpinnerAdapter.clear(); @@ -482,22 +488,14 @@ public class PrintJobConfigActivity extends Activity { @Override protected void onResume() { super.onResume(); - try { - mPrintManager.startDiscoverPrinters(mPrinterDiscoveryObserver); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error starting printer discovery!", re); - } + mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver); notifyPrintableStartIfNeeded(); } @Override protected void onPause() { super.onPause(); - try { - mPrintManager.stopDiscoverPrinters(); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error starting printer discovery!", re); - } + mPrintSpooler.stopPrinterDiscovery(); notifyPrintableFinishIfNeeded(); } @@ -518,119 +516,83 @@ public class PrintJobConfigActivity extends Activity { private void notifyPrintableStartIfNeeded() { if (mDestinationSpinner.getSelectedItemPosition() < 0 - || mPrintStarted) { + || mStarted) { return; } - mPrintStarted = true; - new QueuedAsyncTask<Void>(mTaskQueue) { - @Override - protected Void doInBackground(Void... params) { - try { - mRemotePrintAdapter.start(); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error reading printed data!", ioe); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - updatePrintableContentIfNeeded(); - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + mStarted = true; + mRemotePrintAdapter.start(); + updatePrintableContentIfNeeded(); } private void updatePrintableContentIfNeeded() { - if (!mPrintStarted) { + if (!mStarted) { return; } + // TODO: Implement old attributes tracking mPrintSpooler.setPrintJobAttributes(mPrintJobId, mPrintAttributes); - // TODO: Implement page selector. - final List<PageRange> pages = new ArrayList<PageRange>(); - pages.add(PageRange.ALL_PAGES); - - new QueuedAsyncTask<File>(mTaskQueue) { + mRemotePrintAdapter.layout(new PrintAttributes.Builder().create(), + mPrintAttributes, new LayoutResultCallback() { @Override - protected File doInBackground(Void... params) { - try { - mRemotePrintAdapter.printAttributesChanged(mPrintAttributes); - mRemotePrintAdapter.cancelPrint(); - mRemotePrintAdapter.print(pages); - return mRemotePrintAdapter.getFile(); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error reading printed data!", ioe); - } - return null; + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + // TODO: Handle the case of unchanged content + mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info); + + // TODO: Implement page selector. + final List<PageRange> pages = new ArrayList<PageRange>(); + pages.add(PageRange.ALL_PAGES); + + mRemotePrintAdapter.write(pages, new WriteResultCallback() { + @Override + public void onWriteFinished(List<PageRange> pages) { + updatePrintPreview(mRemotePrintAdapter.getFile()); + } + + @Override + public void onWriteFailed(CharSequence error) { + Log.e(LOG_TAG, "Error write layout: " + error); + finishActivity(Activity.RESULT_CANCELED); + } + }); } @Override - protected void onPostExecute(File file) { - super.onPostExecute(file); - updatePrintPreview(file); + public void onLayoutFailed(CharSequence error) { + Log.e(LOG_TAG, "Error during layout: " + error); + finishActivity(Activity.RESULT_CANCELED); } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + }); } private void notifyPrintableFinishIfNeeded() { - if (!mPrintStarted) { + if (!mStarted) { return; } - mPrintStarted = false; - // Cancel all pending async tasks if the activity was canceled. if (!mPrintConfirmed) { - final int taskCount = mTaskQueue.size(); - for (int i = taskCount - 1; i >= 0; i--) { - mTaskQueue.remove(i).cancel(); - } + mRemotePrintAdapter.cancel(); } + mRemotePrintAdapter.finish(); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - // Notify the app that printing completed. - try { - mRemotePrintAdapter.finish(); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error reading printed data!", ioe); - } - - // If canceled, nothing to do. - if (!mPrintConfirmed) { - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_CANCELED); - return null; - } - - // No printer, nothing to do. - final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); - if (selectedIndex < 0) { - // Update the print job's status. - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_CANCELED); - return null; - } - - // Update the print job's printer. - SpinnerItem<PrinterInfo> printerItem = - mDestinationSpinnerAdapter.getItem(selectedIndex); - PrinterId printerId = printerItem.value.getId(); - mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId); + // If canceled or no printer, nothing to do. + final int selectedIndex = mDestinationSpinner.getSelectedItemPosition(); + if (!mPrintConfirmed || selectedIndex < 0) { + // Update the print job's status. + mPrintSpooler.setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_CANCELED); + return; + } - // Update the print job's status. - mPrintSpooler.setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_QUEUED); - return null; - } + // Update the print job's printer. + SpinnerItem<PrinterInfo> printerItem = + mDestinationSpinnerAdapter.getItem(selectedIndex); + PrinterId printerId = printerItem.value.getId(); + mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printerId); - // Important: If we are canceling, then we do not wait for the write - // to complete since the result will be discarded anyway, we simply - // execute the finish immediately which will interrupt the write. - }.executeOnExecutor(mPrintConfirmed ? AsyncTask.SERIAL_EXECUTOR - : AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + // Update the print job's status. + mPrintSpooler.setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_QUEUED); if (DEBUG) { if (mPrintConfirmed) { @@ -689,30 +651,7 @@ public class PrintJobConfigActivity extends Activity { } } - private abstract class QueuedAsyncTask<T> extends AsyncTask<Void, Void, T> { - - private final List<QueuedAsyncTask<?>> mPendingOrRunningTasks; - - public QueuedAsyncTask(List<QueuedAsyncTask<?>> pendingOrRunningTasks) { - mPendingOrRunningTasks = pendingOrRunningTasks; - } - - @Override - protected void onPreExecute() { - mPendingOrRunningTasks.add(this); - } - - @Override - protected void onPostExecute(T result) { - mPendingOrRunningTasks.remove(this); - } - - public void cancel() { - super.cancel(true); - mPendingOrRunningTasks.remove(this); - } - } - + // Caution: Use this only for debugging private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> { private final File mFile; diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java index 2b27b694f1ac..0546a4316619 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java @@ -16,30 +16,18 @@ package com.android.printspooler; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.content.ComponentName; import android.content.Context; import android.os.AsyncTask; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.ServiceManager; import android.print.IPrintClient; -import android.print.IPrintManager; +import android.print.IPrintSpoolerClient; +import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobInfo; import android.print.PrintManager; +import android.print.PrintDocumentInfo; import android.print.PrinterId; import android.util.AtomicFile; import android.util.Log; @@ -48,6 +36,22 @@ import android.util.Xml; import com.android.internal.util.FastXmlSerializer; +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class PrintSpooler { private static final String LOG_TAG = PrintSpooler.class.getSimpleName(); @@ -64,17 +68,17 @@ public class PrintSpooler { private static final Object sLock = new Object(); - private final Object mLock = new Object(); - private static PrintSpooler sInstance; + private final Object mLock = new Object(); + private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); private final PersistenceManager mPersistanceManager; private final Context mContext; - private final IPrintManager mPrintManager; + public IPrintSpoolerClient mClient; public static PrintSpooler getInstance(Context context) { synchronized (sLock) { @@ -89,8 +93,40 @@ public class PrintSpooler { mContext = context; mPersistanceManager = new PersistenceManager(); mPersistanceManager.readStateLocked(); - mPrintManager = IPrintManager.Stub.asInterface( - ServiceManager.getService("print")); + } + + public void setCleint(IPrintSpoolerClient client) { + synchronized (mLock) { + mClient = client; + } + } + + 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); + } + } + } + + 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); + } + } } public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId) { @@ -102,7 +138,7 @@ public class PrintSpooler { PrinterId printerId = printJob.getPrinterId(); final boolean sameComponent = (componentName == null || (printerId != null - && componentName.equals(printerId.getServiceComponentName()))); + && componentName.equals(printerId.getService()))); final boolean sameAppId = appId == PrintManager.APP_ID_ANY || printJob.getAppId() == appId; final boolean sameState = state == PrintJobInfo.STATE_ANY @@ -137,15 +173,10 @@ public class PrintSpooler { PrintJobInfo printJob = getPrintJob(printJobId, appId); if (printJob != null) { switch (printJob.getState()) { - case PrintJobInfo.STATE_CREATED: { - removePrintJobLocked(printJob); - } return true; + case PrintJobInfo.STATE_CREATED: case PrintJobInfo.STATE_QUEUED: { - removePrintJobLocked(printJob); + setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED); } return true; - default: { - return false; - } } } return false; @@ -169,6 +200,72 @@ public class PrintSpooler { } } + public void notifyClientForActivteJobs() { + IPrintSpoolerClient client = null; + Map<ComponentName, List<PrintJobInfo>> activeJobsPerServiceMap = + new HashMap<ComponentName, List<PrintJobInfo>>(); + + synchronized(mLock) { + if (mClient == null) { + throw new IllegalStateException("Client cannot be null."); + } + client = mClient; + + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + switch (printJob.getState()) { + case PrintJobInfo.STATE_CREATED: { + /* skip - not ready to be handled by a service */ + } break; + + case PrintJobInfo.STATE_QUEUED: + case PrintJobInfo.STATE_STARTED: { + ComponentName service = printJob.getPrinterId().getService(); + List<PrintJobInfo> jobsPerService = activeJobsPerServiceMap.get(service); + if (jobsPerService == null) { + jobsPerService = new ArrayList<PrintJobInfo>(); + activeJobsPerServiceMap.put(service, jobsPerService); + } + jobsPerService.add(printJob); + } break; + + default: { + ComponentName service = printJob.getPrinterId().getService(); + if (!activeJobsPerServiceMap.containsKey(service)) { + activeJobsPerServiceMap.put(service, null); + } + } + } + } + } + + boolean allPrintJobsHandled = true; + + for (Map.Entry<ComponentName, List<PrintJobInfo>> entry + : activeJobsPerServiceMap.entrySet()) { + ComponentName service = entry.getKey(); + List<PrintJobInfo> printJobs = entry.getValue(); + + if (printJobs != null) { + allPrintJobsHandled = false; + final int printJobCount = printJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = printJobs.get(i); + if (printJob.getState() == PrintJobInfo.STATE_QUEUED) { + callOnPrintJobQueuedQuietly(client, printJob); + } + } + } else { + callOnAllPrintJobsForServiceHandledQuietly(client, service); + } + } + + if (allPrintJobsHandled) { + callOnAllPrintJobsHandledQuietly(client); + } + } + private int generatePrintJobIdLocked() { int printJobId = sPrintJobIdCounter++; while (isDuplicatePrintJobId(printJobId)) { @@ -213,24 +310,14 @@ public class PrintSpooler { } catch (IOException ioe) { Log.e(LOG_TAG, "Error writing print job data!", ioe); } finally { - closeIfNotNullNoException(in); - closeIfNotNullNoException(out); - closeIfNotNullNoException(fd); + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + IoUtils.closeQuietly(fd); } } return false; } - private void closeIfNotNullNoException(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException ioe) { - /* ignore */; - } - } - } - public File generateFileForPrintJob(int printJobId) { return new File(mContext.getFilesDir(), "print_job_" + printJobId + "." + PRINT_FILE_EXTENSION); @@ -254,9 +341,20 @@ 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 = getPrintJob(printJobId, PrintManager.APP_ID_ANY); if (printJob != null && printJob.getState() < state) { success = true; @@ -265,8 +363,23 @@ public class PrintSpooler { switch (state) { case PrintJobInfo.STATE_COMPLETED: case PrintJobInfo.STATE_CANCELED: { + removedPrintJob = printJob; removePrintJobLocked(printJob); + + // No printer means creation of a print job was cancelled, + // therefore the state of the spooler did not change and no + // notifications are needed. We also do not need to persist + // the state. + PrinterId printerId = printJob.getPrinterId(); + if (printerId == null) { + return true; + } + + allPrintJobsHandled = !hasActivePrintJobsLocked(); + allPrintJobsForServiceHandled = !hasActivePrintJobsForServiceLocked( + printerId.getService()); } break; + case PrintJobInfo.STATE_QUEUED: { queuedPrintJob = new PrintJobInfo(printJob); } break; @@ -279,17 +392,77 @@ public class PrintSpooler { } if (queuedPrintJob != null) { - try { - mPrintManager.onPrintJobQueued(queuedPrintJob.getPrinterId(), - queuedPrintJob); - } catch (RemoteException re) { - /* ignore */ - } + 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(IPrintSpoolerClient client) { + try { + client.onAllPrintJobsHandled(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error notify for all print job handled.", re); + } + } + + 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; + } + } + } + return false; + } + + private boolean hasActivePrintJobsForServiceLocked(ComponentName service) { + 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; + } + } + return false; + } + public boolean setPrintJobTag(int printJobId, String tag) { synchronized (mLock) { PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); @@ -302,6 +475,18 @@ public class PrintSpooler { return false; } + public final boolean setPrintJobPrintDocumentInfo(int printJobId, PrintDocumentInfo info) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setDocumentInfo(info); + mPersistanceManager.writeStateLocked(); + return true; + } + } + return false; + } + public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) { synchronized (mLock) { PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 57c45577f8b9..050332cc0dc0 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -29,10 +29,11 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.print.IPrintAdapter; +import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; -import android.print.IPrintSpoolerService; -import android.print.IPrintSpoolerServiceCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.IPrintSpooler; +import android.print.IPrintSpoolerCallbacks; import android.print.PrintAttributes; import android.print.PrintJobInfo; import android.util.Slog; @@ -64,21 +65,21 @@ public final class PrintSpoolerService extends Service { @Override public IBinder onBind(Intent intent) { - return new IPrintSpoolerService.Stub() { + return new IPrintSpooler.Stub() { @Override - public void getPrintJobs(IPrintSpoolerServiceCallbacks callback, + public void getPrintJobInfos(IPrintSpoolerCallbacks callback, ComponentName componentName, int state, int appId, int sequence) throws RemoteException { List<PrintJobInfo> printJobs = null; try { printJobs = mSpooler.getPrintJobs(componentName, state, appId); } finally { - callback.onGetPrintJobsResult(printJobs, sequence); + callback.onGetPrintJobInfosResult(printJobs, sequence); } } @Override - public void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + public void getPrintJobInfo(int printJobId, IPrintSpoolerCallbacks callback, int appId, int sequence) throws RemoteException { PrintJobInfo printJob = null; try { @@ -89,7 +90,7 @@ public final class PrintSpoolerService extends Service { } @Override - public void cancelPrintJob(int printJobId, IPrintSpoolerServiceCallbacks callback, + public void cancelPrintJob(int printJobId, IPrintSpoolerCallbacks callback, int appId, int sequence) throws RemoteException { boolean success = false; try { @@ -102,8 +103,8 @@ public final class PrintSpoolerService extends Service { @SuppressWarnings("deprecation") @Override public void createPrintJob(String printJobName, IPrintClient client, - IPrintAdapter printAdapter, PrintAttributes attributes, - IPrintSpoolerServiceCallbacks callback, int appId, int sequence) + IPrintDocumentAdapter printAdapter, PrintAttributes attributes, + IPrintSpoolerCallbacks callback, int appId, int sequence) throws RemoteException { PrintJobInfo printJob = null; try { @@ -134,7 +135,7 @@ public final class PrintSpoolerService extends Service { @Override public void setPrintJobState(int printJobId, int state, - IPrintSpoolerServiceCallbacks callback, int sequece) + IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { boolean success = false; try { @@ -148,7 +149,7 @@ public final class PrintSpoolerService extends Service { @Override public void setPrintJobTag(int printJobId, String tag, - IPrintSpoolerServiceCallbacks callback, int sequece) + IPrintSpoolerCallbacks callback, int sequece) throws RemoteException { boolean success = false; try { @@ -162,6 +163,16 @@ public final class PrintSpoolerService extends Service { public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { mSpooler.writePrintJobData(fd, printJobId); } + + @Override + public void setClient(IPrintSpoolerClient client) { + mSpooler.setCleint(client); + } + + @Override + public void notifyClientForActivteJobs() { + mSpooler.notifyClientForActivteJobs(); + } }; } diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java deleted file mode 100644 index c81b00c6b167..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java +++ /dev/null @@ -1,219 +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 com.android.printspooler; - -import android.os.ICancellationSignal; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.print.IPrintAdapter; -import android.print.IPrintResultCallback; -import android.print.PageRange; -import android.print.PrintAdapterInfo; -import android.print.PrintAttributes; -import android.util.Log; - -import libcore.io.IoUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -/** - * This class represents a remote print adapter instance. - */ -final class RemotePrintAdapter { - private static final String LOG_TAG = "RemotePrintAdapter"; - - private static final boolean DEBUG = true; - - private final Object mLock = new Object(); - - private final IPrintAdapter mRemoteInterface; - - private final File mFile; - - private final IPrintResultCallback mIPrintProgressListener; - - private PrintAdapterInfo mInfo; - - private ICancellationSignal mCancellationSignal; - - private Thread mWriteThread; - - public RemotePrintAdapter(IPrintAdapter printAdatper, File file) { - mRemoteInterface = printAdatper; - mFile = file; - mIPrintProgressListener = new IPrintResultCallback.Stub() { - @Override - public void onPrintStarted(PrintAdapterInfo info, - ICancellationSignal cancellationSignal) { - if (DEBUG) { - Log.i(LOG_TAG, "IPrintProgressListener#onPrintStarted()"); - } - synchronized (mLock) { - mInfo = info; - mCancellationSignal = cancellationSignal; - } - } - - @Override - public void onPrintFinished(List<PageRange> pages) { - if (DEBUG) { - Log.i(LOG_TAG, "IPrintProgressListener#onPrintFinished(" + pages + ")"); - } - synchronized (mLock) { - if (isPrintingLocked()) { - mWriteThread.interrupt(); - mCancellationSignal = null; - } - } - } - - @Override - public void onPrintFailed(CharSequence error) { - if (DEBUG) { - Log.i(LOG_TAG, "IPrintProgressListener#onPrintFailed(" + error + ")"); - } - synchronized (mLock) { - if (isPrintingLocked()) { - mWriteThread.interrupt(); - mCancellationSignal = null; - } - } - } - }; - } - - public File getFile() { - if (DEBUG) { - Log.i(LOG_TAG, "getFile()"); - } - return mFile; - } - - public void start() throws IOException { - if (DEBUG) { - Log.i(LOG_TAG, "start()"); - } - try { - mRemoteInterface.start(); - } catch (RemoteException re) { - throw new IOException("Error reading file", re); - } - } - - public void printAttributesChanged(PrintAttributes attributes) throws IOException { - if (DEBUG) { - Log.i(LOG_TAG, "printAttributesChanged(" + attributes +")"); - } - try { - mRemoteInterface.printAttributesChanged(attributes); - } catch (RemoteException re) { - throw new IOException("Error reading file", re); - } - } - - public void print(List<PageRange> pages) throws IOException { - if (DEBUG) { - Log.i(LOG_TAG, "print(" + pages +")"); - } - 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.print(pages, sink, mIPrintProgressListener); - - // 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); - } - } catch (RemoteException re) { - throw new IOException("Error reading file", re); - } catch (IOException ioe) { - throw new IOException("Error reading file", ioe); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(sink); - IoUtils.closeQuietly(source); - } - } - - public void cancelPrint() throws IOException { - if (DEBUG) { - Log.i(LOG_TAG, "cancelPrint()"); - } - synchronized (mLock) { - if (isPrintingLocked()) { - try { - mCancellationSignal.cancel(); - } catch (RemoteException re) { - throw new IOException("Error cancelling print", re); - } - } - } - } - - public void finish() throws IOException { - if (DEBUG) { - Log.i(LOG_TAG, "finish()"); - } - try { - mRemoteInterface.finish(); - } catch (RemoteException re) { - throw new IOException("Error reading file", re); - } - } - - public PrintAdapterInfo getInfo() { - synchronized (mLock) { - return mInfo; - } - } - - private boolean isPrintingLocked() { - return mCancellationSignal != null; - } -} diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java new file mode 100644 index 000000000000..912dd95b1663 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -0,0 +1,448 @@ +/* + * 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 com.android.printspooler; + +import android.os.AsyncTask; +import android.os.ICancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.print.ILayoutResultCallback; +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; + +import java.io.File; +import java.io.FileInputStream; +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. + */ +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_COMPLETED = 2; + public static final int STATE_WRITE_COMPLETED = 3; + public static final int STATE_FINISH_COMPLETED = 4; + + private final Object mLock = new Object(); + + private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>(); + + 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() { + if (DEBUG) { + Log.i(LOG_TAG, "getFile()"); + } + synchronized (mLock) { + if (mState < STATE_WRITE_COMPLETED) { + throw new IllegalStateException("Write not completed"); + } + return mFile; + } + } + + public void cancel() { + synchronized (mLock) { + final int taskCount = mTaskQueue.size(); + for (int i = 0; i < taskCount; i++) { + mTaskQueue.remove(i).cancel(); + } + } + } + + 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; + } + }; + synchronized (mLock) { + mTaskQueue.add(task); + task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + } + + public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, + LayoutResultCallback callback) { + LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback); + synchronized (mLock) { + mTaskQueue.add(task); + task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + } + + public void write(List<PageRange> pages, WriteResultCallback callback) { + WriteAsyncTask task = new WriteAsyncTask(pages, callback); + mTaskQueue.add(task); + task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + public void finish() { + QueuedAsyncTask task = new QueuedAsyncTask() { + @Override + protected Void doInBackground(Void... params) { + if (DEBUG) { + Log.i(LOG_TAG, "finish"); + } + synchronized (mLock) { + if (mState != STATE_LAYOUT_COMPLETED + && mState != STATE_WRITE_COMPLETED) { + throw new IllegalStateException("Invalid state: " + mState); + } + } + try { + mRemoteInterface.finish(); + synchronized (mLock) { + mState = STATE_FINISH_COMPLETED; + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error reading file", re); + mState = STATE_INITIALIZED; + } + return null; + } + }; + 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 ILayoutResultCallback mILayoutResultCallback = + new ILayoutResultCallback.Stub() { + @Override + public void onLayoutStarted(ICancellationSignal cancellationSignal) { + synchronized (mLock) { + mCancellationSignal = cancellationSignal; + if (isCancelled()) { + cancelSignalQuietlyLocked(); + } + } + } + + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + synchronized (mLock) { + mCancellationSignal = null; + mCompleted = true; + mLock.notifyAll(); + } + mCallback.onLayoutFinished(info, changed); + } + + @Override + public void onLayoutFailed(CharSequence error) { + synchronized (mLock) { + mCancellationSignal = null; + mCompleted = true; + mLock.notifyAll(); + } + Slog.e(LOG_TAG, "Error laying out print document: " + error); + mCallback.onLayoutFailed(error); + } + }; + + private ICancellationSignal mCancellationSignal; + + private boolean mCompleted; + + public LayoutAsyncTask(PrintAttributes oldAttributes, + PrintAttributes newAttributes, LayoutResultCallback callback) { + mOldAttributes = oldAttributes; + mNewAttributes = newAttributes; + mCallback = callback; + } + + @Override + public void cancel() { + synchronized (mLock) { + throwIfCancelledLocked(); + cancelSignalQuietlyLocked(); + } + super.cancel(); + } + + @Override + protected Void doInBackground(Void... params) { + synchronized (mLock) { + if (mState != STATE_START_COMPLETED + && mState != STATE_LAYOUT_COMPLETED + && mState != STATE_WRITE_COMPLETED) { + throw new IllegalStateException("Invalid state: " + mState); + } + } + try { + mRemoteInterface.layout(mOldAttributes, mNewAttributes, + mILayoutResultCallback); + synchronized (mLock) { + while (true) { + if (isCancelled()) { + mState = STATE_INITIALIZED; + mTaskQueue.remove(this); + break; + } + if (mCompleted) { + mState = STATE_LAYOUT_COMPLETED; + mTaskQueue.remove(this); + break; + } + try { + mLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error calling layout", re); + mState = STATE_INITIALIZED; + } + return null; + } + + private void cancelSignalQuietlyLocked() { + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error cancelling layout", re); + } + } + } + + private void throwIfCancelledLocked() { + if (isCancelled()) { + throw new IllegalStateException("Already cancelled"); + } + } + } + + private final class WriteAsyncTask extends QueuedAsyncTask { + + private final List<PageRange> mPages; + + private final WriteResultCallback mCallback; + + private final IWriteResultCallback mIWriteResultCallback = + new IWriteResultCallback.Stub() { + @Override + public void onWriteStarted(ICancellationSignal cancellationSignal) { + synchronized (mLock) { + mCancellationSignal = cancellationSignal; + if (isCancelled()) { + cancelSignalQuietlyLocked(); + } + } + } + + @Override + public void onWriteFinished(List<PageRange> pages) { + synchronized (mLock) { + mCancellationSignal = null; + mCompleted = true; + mLock.notifyAll(); + } + mCallback.onWriteFinished(pages); + } + + @Override + public void onWriteFailed(CharSequence error) { + synchronized (mLock) { + mCancellationSignal = null; + mCompleted = true; + mLock.notifyAll(); + } + Slog.e(LOG_TAG, "Error writing print document: " + error); + mCallback.onWriteFailed(error); + } + }; + + private ICancellationSignal mCancellationSignal; + + private boolean mCompleted; + + 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, "print()"); + } + synchronized (mLock) { + if (mState != STATE_LAYOUT_COMPLETED) { + throw new IllegalStateException("Invalid state: " + mState); + } + } + 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 (isCancelled()) { + mState = STATE_INITIALIZED; + mTaskQueue.remove(this); + break; + } + if (mCompleted) { + mState = STATE_WRITE_COMPLETED; + mTaskQueue.remove(this); + break; + } + try { + mLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error writing print document", re); + mState = STATE_INITIALIZED; + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing print document", ioe); + mState = STATE_INITIALIZED; + } 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); + } + } + } + + private void throwIfCancelledLocked() { + if (isCancelled()) { + throw new IllegalStateException("Already cancelled"); + } + } + } +} diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 5b76f3954d97..203cca692df0 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -70,7 +70,7 @@ class AppWidgetService extends IAppWidgetService.Stub mAppWidgetServices.append(0, primary); } - public void systemReady(boolean safeMode) { + public void systemRunning(boolean safeMode) { mSafeMode = safeMode; mAppWidgetServices.get(0).systemReady(safeMode); diff --git a/services/java/com/android/server/AssetAtlasService.java b/services/java/com/android/server/AssetAtlasService.java index 33f082c00464..26b4652f4c7d 100644 --- a/services/java/com/android/server/AssetAtlasService.java +++ b/services/java/com/android/server/AssetAtlasService.java @@ -186,7 +186,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { * Callback invoked by the server thread to indicate we can now run * 3rd party code. */ - public void systemReady() { + public void systemRunning() { } /** diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java index c316733fe058..aa2c8b88a77e 100644 --- a/services/java/com/android/server/CommonTimeManagementService.java +++ b/services/java/com/android/server/CommonTimeManagementService.java @@ -153,7 +153,7 @@ class CommonTimeManagementService extends Binder { mContext = context; } - void systemReady() { + void systemRunning() { if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) { Log.i(TAG, "No common time service detected on this platform. " + "Common time services will be unavailable."); diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java index 8407fa44648f..4956dd5bd80a 100644 --- a/services/java/com/android/server/CountryDetectorService.java +++ b/services/java/com/android/server/CountryDetectorService.java @@ -166,7 +166,7 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run } } - void systemReady() { + void systemRunning() { // Shall we wait for the initialization finish. BackgroundThread.getHandler().post(this); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c0a521480528..35656f8367c2 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -823,7 +823,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - public void systemReady(StatusBarManagerService statusBar) { + public void systemRunning(StatusBarManagerService statusBar) { synchronized (mMethodMap) { if (DEBUG) { Slog.d(TAG, "--- systemReady"); diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index c6c9845979ad..bde9e1c95150 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -198,7 +198,7 @@ public class LocationManagerService extends ILocationManager.Stub { // most startup is deferred until systemReady() } - public void systemReady() { + public void systemRunning() { synchronized (mLock) { if (D) Log.d(TAG, "systemReady()"); diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index 02b42b81de67..cbddf6797797 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -108,7 +108,7 @@ public class NetworkTimeUpdateService { } /** Initialize the receivers and initiate the first NTP request */ - public void systemReady() { + public void systemRunning() { registerForTelephonyIntents(); registerForAlarms(); registerForConnectivityIntents(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 888064ccc7ee..0bbdcfb230e3 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -351,6 +351,7 @@ class ServerThread { LockSettingsService lockSettings = null; DreamManagerService dreamy = null; AssetAtlasService atlas = null; + PrintManagerService printManager = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { @@ -793,8 +794,8 @@ class ServerThread { try { Slog.i(TAG, "Print Service"); - ServiceManager.addService(Context.PRINT_SERVICE, - new PrintManagerService(context)); + printManager = new PrintManagerService(context); + ServiceManager.addService(Context.PRINT_SERVICE, printManager); } catch (Throwable e) { reportWtf("starting Print Service", e); } @@ -909,6 +910,7 @@ class ServerThread { final AssetAtlasService atlasF = atlas; final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; + final PrintManagerService printManagerF = printManager; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -988,66 +990,73 @@ class ServerThread { // third party code... try { - if (appWidgetF != null) appWidgetF.systemReady(safeMode); + if (appWidgetF != null) appWidgetF.systemRunning(safeMode); } catch (Throwable e) { - reportWtf("making App Widget Service ready", e); + reportWtf("Notifying AppWidgetService running", e); } try { - if (wallpaperF != null) wallpaperF.systemReady(); + if (wallpaperF != null) wallpaperF.systemRunning(); } catch (Throwable e) { - reportWtf("making Wallpaper Service ready", e); + reportWtf("Notifying WallpaperService running", e); } try { - if (immF != null) immF.systemReady(statusBarF); + if (immF != null) immF.systemRunning(statusBarF); } catch (Throwable e) { - reportWtf("making Input Method Service ready", e); + reportWtf("Notifying InputMethodService running", e); } try { - if (locationF != null) locationF.systemReady(); + if (locationF != null) locationF.systemRunning(); } catch (Throwable e) { - reportWtf("making Location Service ready", e); + reportWtf("Notifying Location Service running", e); } try { - if (countryDetectorF != null) countryDetectorF.systemReady(); + if (countryDetectorF != null) countryDetectorF.systemRunning(); } catch (Throwable e) { - reportWtf("making Country Detector Service ready", e); + reportWtf("Notifying CountryDetectorService running", e); } try { - if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); + if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning(); } catch (Throwable e) { - reportWtf("making Network Time Service ready", e); + reportWtf("Notifying NetworkTimeService running", e); } try { - if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady(); + if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemRunning(); } catch (Throwable e) { - reportWtf("making Common time management service ready", e); + reportWtf("Notifying CommonTimeManagementService running", e); } try { - if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); + if (textServiceManagerServiceF != null) + textServiceManagerServiceF.systemRunning(); } catch (Throwable e) { - reportWtf("making Text Services Manager Service ready", e); + reportWtf("Notifying TextServicesManagerService running", e); } try { - if (dreamyF != null) dreamyF.systemReady(); + if (dreamyF != null) dreamyF.systemRunning(); } catch (Throwable e) { - reportWtf("making DreamManagerService ready", e); + reportWtf("Notifying DreamManagerService running", e); } try { - if (atlasF != null) atlasF.systemReady(); + if (atlasF != null) atlasF.systemRunning(); } catch (Throwable e) { - reportWtf("making AssetAtlasService ready", e); + reportWtf("Notifying AssetAtlasService running", e); } try { // TODO(BT) Pass parameter to input manager - if (inputManagerF != null) inputManagerF.systemReady(); + if (inputManagerF != null) inputManagerF.systemRunning(); } catch (Throwable e) { - reportWtf("making InputManagerService ready", e); + reportWtf("Notifying InputManagerService running", e); } try { - if (telephonyRegistryF != null) telephonyRegistryF.systemReady(); + if (telephonyRegistryF != null) telephonyRegistryF.systemRunning(); } catch (Throwable e) { - reportWtf("making TelephonyRegistry ready", e); + reportWtf("Notifying TelephonyRegistry running", e); + } + + try { + if (printManagerF != null) printManagerF.systemRuning(); + } catch (Throwable e) { + reportWtf("Notifying PrintManagerService running", e); } } }); diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 17260d505fdb..699d79eb0953 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -178,7 +178,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mConnectedApns = new ArrayList<String>(); } - public void systemReady() { + public void systemRunning() { // Watch for interesting updates final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java index 7dd9988b93af..6587c416230d 100644 --- a/services/java/com/android/server/TextServicesManagerService.java +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -76,7 +76,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { new HashMap<String, SpellCheckerBindGroup>(); private final TextServicesSettings mSettings; - public void systemReady() { + public void systemRunning() { if (!mSystemReady) { mSystemReady = true; } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 9a7390980405..d677f2497cbd 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -449,7 +449,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - public void systemReady() { + public void systemRunning() { if (DEBUG) Slog.v(TAG, "systemReady"); WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER); switchWallpaper(wallpaper, null); diff --git a/services/java/com/android/server/dreams/DreamManagerService.java b/services/java/com/android/server/dreams/DreamManagerService.java index c9e0da5b8f54..21e54fec863b 100644 --- a/services/java/com/android/server/dreams/DreamManagerService.java +++ b/services/java/com/android/server/dreams/DreamManagerService.java @@ -73,7 +73,7 @@ public final class DreamManagerService extends IDreamManager.Stub { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); } - public void systemReady() { + public void systemRunning() { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index 5e4907e6820d..7b4c0775e47e 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -283,7 +283,7 @@ public class InputManagerService extends IInputManager.Stub } // TODO(BT) Pass in paramter for bluetooth system - public void systemReady() { + public void systemRunning() { if (DEBUG) { Slog.d(TAG, "System ready."); } diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java index 51739984c613..86e76852b4d5 100644 --- a/services/java/com/android/server/print/PrintManagerService.java +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -22,118 +22,111 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; import android.os.Process; -import android.os.RemoteException; import android.os.UserHandle; -import android.print.IPrintAdapter; import android.print.IPrintClient; +import android.print.IPrintDocumentAdapter; import android.print.IPrintManager; -import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobInfo; -import android.print.PrintManager; -import android.print.PrinterId; -import android.print.PrinterInfo; -import android.printservice.IPrintService; -import android.printservice.IPrintServiceClient; -import android.printservice.PrintServiceInfo; import android.provider.Settings; -import android.text.TextUtils; -import android.text.TextUtils.SimpleStringSplitter; -import android.util.Slog; +import android.util.SparseArray; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; public final class PrintManagerService extends IPrintManager.Stub { - private static final String LOG_TAG = PrintManagerService.class.getSimpleName(); - private static final char COMPONENT_NAME_SEPARATOR = ':'; private final Object mLock = new Object(); - private final SimpleStringSplitter mStringColonSplitter = - new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); - - private final Map<ComponentName, PrintServiceClient> mServices = - new HashMap<ComponentName, PrintServiceClient>(); - - private final List<PrintServiceInfo> mInstalledServices = new ArrayList<PrintServiceInfo>(); - - private final Set<ComponentName> mEnabledServiceNames = new HashSet<ComponentName>(); - private final Context mContext; - private final RemoteSpooler mSpooler; - - private final int mMyUid; + private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); private int mCurrentUserId = UserHandle.USER_OWNER; - private IPrinterDiscoveryObserver mPrinterDiscoveryObserver; - public PrintManagerService(Context context) { mContext = context; - mSpooler = new RemoteSpooler(context); - mMyUid = android.os.Process.myUid(); registerContentObservers(); - registerBoradcastreceivers(); + registerBoradcastReceivers(); + } + + public void systemRuning() { + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + userState.getSpoolerLocked().notifyClientForActivteJobs(); + } + } + }); } @Override - public PrintJobInfo print(String printJobName, IPrintClient client, IPrintAdapter printAdapter, - PrintAttributes attributes, int appId, int userId) { - final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId); - final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId); + public PrintJobInfo print(String printJobName, IPrintClient client, + IPrintDocumentAdapter documentAdapter, PrintAttributes attributes, int appId, + int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + final RemotePrintSpooler spooler; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + spooler = userState.getSpoolerLocked(); + } final long identity = Binder.clearCallingIdentity(); try { - return mSpooler.createPrintJob(printJobName, client, printAdapter, - attributes, resolvedAppId, resolvedUserId); + return spooler.createPrintJob(printJobName, client, documentAdapter, + attributes, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public List<PrintJobInfo> getPrintJobs(int appId, int userId) { - final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId); - final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId); - // TODO: Do we want to return jobs in STATE_CREATED? We should probably - // have additional argument for the types to get + public List<PrintJobInfo> getPrintJobInfos(int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + final RemotePrintSpooler spooler; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + spooler = userState.getSpoolerLocked(); + } final long identity = Binder.clearCallingIdentity(); try { - return mSpooler.getPrintJobs(null, PrintJobInfo.STATE_ANY, - resolvedAppId, resolvedUserId); + return spooler.getPrintJobInfos(null, PrintJobInfo.STATE_ANY, + resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public PrintJobInfo getPrintJob(int printJobId, int appId, int userId) { - final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId); - final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId); + public PrintJobInfo getPrintJobInfo(int printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + final RemotePrintSpooler spooler; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + spooler = userState.getSpoolerLocked(); + } final long identity = Binder.clearCallingIdentity(); try { - return mSpooler.getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId); + return spooler.getPrintJobInfo(printJobId, resolvedAppId); } finally { Binder.restoreCallingIdentity(identity); } @@ -141,93 +134,32 @@ public final class PrintManagerService extends IPrintManager.Stub { @Override public void cancelPrintJob(int printJobId, int appId, int userId) { - final int resolvedAppId = resolveCallingAppEnforcingPermissionsLocked(appId); - final int resolvedUserId = resolveCallingUserEnforcingPermissionsIdLocked(userId); + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + final RemotePrintSpooler spooler; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + spooler = userState.getSpoolerLocked(); + } final long identity = Binder.clearCallingIdentity(); try { - if (mSpooler.cancelPrintJob(printJobId, resolvedAppId, resolvedUserId)) { + if (spooler.cancelPrintJob(printJobId, resolvedAppId)) { return; } - PrintJobInfo printJob = getPrintJob(printJobId, resolvedAppId, resolvedUserId); - if (printJob == null) { + PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, resolvedAppId, resolvedUserId); + if (printJobInfo == null) { return; } - ComponentName printServiceName = printJob.getPrinterId().getServiceComponentName(); - PrintServiceClient printService = null; + ComponentName printServiceName = printJobInfo.getPrinterId().getService(); + RemotePrintService printService = null; synchronized (mLock) { - printService = mServices.get(printServiceName); + printService = userState.getActiveServices().get(printServiceName); } if (printService == null) { return; } - printService.requestCancelPrintJob(printJob); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - // Called only from the spooler. - @Override - public void onPrintJobQueued(PrinterId printerId, PrintJobInfo printJob) { - throwIfCallerNotSignedWithSystemKey(); - PrintServiceClient printService = null; - synchronized (mLock) { - ComponentName printServiceName = printerId.getServiceComponentName(); - printService = mServices.get(printServiceName); - } - if (printService != null) { - final long identity = Binder.clearCallingIdentity(); - try { - printService.notifyPrintJobQueued(printJob); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - // Called only from the spooler. - @Override - public void startDiscoverPrinters(IPrinterDiscoveryObserver observer) { - throwIfCallerNotSignedWithSystemKey(); - List<PrintServiceClient> services = new ArrayList<PrintServiceClient>(); - synchronized (mLock) { - mPrinterDiscoveryObserver = observer; - services.addAll(mServices.values()); - } - final int serviceCount = services.size(); - if (serviceCount <= 0) { - return; - } - final long identity = Binder.clearCallingIdentity(); - try { - for (int i = 0; i < serviceCount; i++) { - PrintServiceClient service = services.get(i); - service.startPrinterDiscovery(); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - // Called only from the spooler. - @Override - public void stopDiscoverPrinters() { - throwIfCallerNotSignedWithSystemKey(); - List<PrintServiceClient> services = new ArrayList<PrintServiceClient>(); - synchronized (mLock) { - mPrinterDiscoveryObserver = null; - services.addAll(mServices.values()); - } - final int serviceCount = services.size(); - if (serviceCount <= 0) { - return; - } - final long identity = Binder.clearCallingIdentity(); - try { - for (int i = 0; i < serviceCount; i++) { - PrintServiceClient service = services.get(i); - service.stopPrintersDiscovery(); - } + printService.onRequestCancelPrintJob(printJobInfo); } finally { Binder.restoreCallingIdentity(identity); } @@ -237,14 +169,13 @@ public final class PrintManagerService extends IPrintManager.Stub { final Uri enabledPrintServicesUri = Settings.Secure.getUriFor( Settings.Secure.ENABLED_PRINT_SERVICES); - ContentObserver observer = new ContentObserver(new Handler(mContext.getMainLooper())) { + ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) { @Override public void onChange(boolean selfChange, Uri uri) { if (enabledPrintServicesUri.equals(uri)) { synchronized (mLock) { - if (readEnabledPrintServicesChangedLocked()) { - onUserStateChangedLocked(); - } + UserState userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); } } } @@ -254,32 +185,37 @@ public final class PrintManagerService extends IPrintManager.Stub { false, observer, UserHandle.USER_ALL); } - private void registerBoradcastreceivers() { + private void registerBoradcastReceivers() { PackageMonitor monitor = new PackageMonitor() { @Override - public void onSomePackagesChanged() { + public boolean onPackageChanged(String packageName, int uid, String[] components) { synchronized (mLock) { - if (getChangingUserId() != mCurrentUserId) { - return; - } - if (readConfigurationForUserStateLocked()) { - onUserStateChangedLocked(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + if (packageName.equals(componentName.getPackageName())) { + userState.updateIfNeededLocked(); + return true; + } } } + return false; } @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { - if (getChangingUserId() != mCurrentUserId) { - return; - } - Iterator<ComponentName> iterator = mEnabledServiceNames.iterator(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); while (iterator.hasNext()) { ComponentName componentName = iterator.next(); if (packageName.equals(componentName.getPackageName())) { iterator.remove(); - onEnabledServiceNamesChangedLocked(); + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_PRINT_SERVICES, + userState.getEnabledServices(), getChangingUserId()); + userState.updateIfNeededLocked(); return; } } @@ -290,10 +226,9 @@ public final class PrintManagerService extends IPrintManager.Stub { public boolean onHandleForceStop(Intent intent, String[] stoppedPackages, int uid, boolean doit) { synchronized (mLock) { - if (getChangingUserId() != mCurrentUserId) { - return false; - } - Iterator<ComponentName> iterator = mEnabledServiceNames.iterator(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + boolean stoppedSomePackages = false; + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); while (iterator.hasNext()) { ComponentName componentName = iterator.next(); String componentPackage = componentName.getPackageName(); @@ -302,27 +237,35 @@ public final class PrintManagerService extends IPrintManager.Stub { if (!doit) { return true; } - iterator.remove(); - onEnabledServiceNamesChangedLocked(); + stoppedSomePackages = true; + break; } } } + if (stoppedSomePackages) { + userState.updateIfNeededLocked(); + } return false; } } - private void onEnabledServiceNamesChangedLocked() { - // Update the enabled services setting. - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_PRINT_SERVICES, - mEnabledServiceNames, mCurrentUserId); - // Update the current user state. - onUserStateChangedLocked(); + private void persistComponentNamesToSettingLocked(String settingName, + Set<ComponentName> componentNames, int userId) { + StringBuilder builder = new StringBuilder(); + for (ComponentName componentName : componentNames) { + if (builder.length() > 0) { + builder.append(COMPONENT_NAME_SEPARATOR); + } + builder.append(componentName.flattenToShortString()); + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + settingName, builder.toString(), userId); } }; // package changes - monitor.register(mContext, null, UserHandle.ALL, true); + monitor.register(mContext, BackgroundThread.getHandler().getLooper(), + UserHandle.ALL, true); // user changes IntentFilter intentFilter = new IntentFilter(); @@ -334,179 +277,67 @@ public final class PrintManagerService extends IPrintManager.Stub { String action = intent.getAction(); if (Intent.ACTION_USER_SWITCHED.equals(action)) { switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } } - }, UserHandle.ALL, intentFilter, null, null); - } - - private void throwIfCallerNotSignedWithSystemKey() { - if (mContext.getPackageManager().checkSignatures( - mMyUid, Binder.getCallingUid()) != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException("Caller must be signed with the system key!"); - } - } - - private void onUserStateChangedLocked() { - manageServicesLocked(); - } - - private void manageServicesLocked() { - final int installedCount = mInstalledServices.size(); - for (int i = 0; i < installedCount; i++) { - ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo(); - ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName, - resolveInfo.serviceInfo.name); - if (mEnabledServiceNames.contains(serviceName)) { - if (!mServices.containsKey(serviceName)) { - new PrintServiceClient(serviceName, mCurrentUserId).ensureBoundLocked(); - } - } else { - PrintServiceClient service = mServices.get(serviceName); - if (service != null) { - service.ensureUnboundLocked(); - } - } - } + }, UserHandle.ALL, intentFilter, null, BackgroundThread.getHandler()); } - private boolean readConfigurationForUserStateLocked() { - boolean somethingChanged = false; - somethingChanged |= readInstalledPrintServiceLocked(); - somethingChanged |= readEnabledPrintServicesChangedLocked(); - return somethingChanged; - } - - private boolean readEnabledPrintServicesChangedLocked() { - Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>(); - readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, - mCurrentUserId, tempEnabledServiceNameSet); - if (!tempEnabledServiceNameSet.equals(mEnabledServiceNames)) { - mEnabledServiceNames.clear(); - mEnabledServiceNames.addAll(tempEnabledServiceNameSet); - return true; - } - return false; + private UserState getCurrentUserStateLocked() { + return getOrCreateUserStateLocked(mCurrentUserId); } - private boolean readInstalledPrintServiceLocked() { - Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>(); - - List<ResolveInfo> installedServices = mContext.getPackageManager() - .queryIntentServicesAsUser( - new Intent(android.printservice.PrintService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - mCurrentUserId); - - final int installedCount = installedServices.size(); - for (int i = 0, count = installedCount; i < count; i++) { - ResolveInfo installedService = installedServices.get(i); - if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals( - installedService.serviceInfo.permission)) { - ComponentName serviceName = new ComponentName( - installedService.serviceInfo.packageName, - installedService.serviceInfo.name); - Slog.w(LOG_TAG, "Skipping print service " - + serviceName.flattenToShortString() - + " since it does not require permission " - + android.Manifest.permission.BIND_PRINT_SERVICE); - continue; - } - tempPrintServices.add(PrintServiceInfo.create(installedService, mContext)); - } - - if (!tempPrintServices.equals(mInstalledServices)) { - mInstalledServices.clear(); - mInstalledServices.addAll(tempPrintServices); - return true; + private UserState getOrCreateUserStateLocked(int userId) { + UserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new UserState(mContext, userId, mLock); + mUserStates.put(userId, userState); } - return false; - } - - private void readComponentNamesFromSettingLocked(String settingName, int userId, - Set<ComponentName> outComponentNames) { - String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), - settingName, userId); - outComponentNames.clear(); - if (!TextUtils.isEmpty(settingValue)) { - TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(settingValue); - while (splitter.hasNext()) { - String string = splitter.next(); - if (TextUtils.isEmpty(string)) { - continue; - } - ComponentName componentName = ComponentName.unflattenFromString(string); - if (componentName != null) { - outComponentNames.add(componentName); - } - } - } - } - - private void persistComponentNamesToSettingLocked(String settingName, - Set<ComponentName> componentNames, int userId) { - StringBuilder builder = new StringBuilder(); - for (ComponentName componentName : componentNames) { - if (builder.length() > 0) { - builder.append(COMPONENT_NAME_SEPARATOR); - } - builder.append(componentName.flattenToShortString()); - } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - settingName, builder.toString(), userId); + return userState; } private void switchUser(int newUserId) { synchronized (mLock) { - // Disconnect services for the old user. - mEnabledServiceNames.clear(); - onUserStateChangedLocked(); - - // The user changed. + if (newUserId == mCurrentUserId) { + return; + } mCurrentUserId = newUserId; - - // Update the user state based on current settings. - readConfigurationForUserStateLocked(); - onUserStateChangedLocked(); - } - - // Unbind the spooler for the old user). - mSpooler.unbind(); - - // If we have queued jobs, advertise it, or we do - // not need the spooler for now. - if (notifyQueuedPrintJobs()) { - mSpooler.unbind(); + UserState userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + userState.getSpoolerLocked().notifyClientForActivteJobs(); } } - private boolean notifyQueuedPrintJobs() { - Map<PrintServiceClient, List<PrintJobInfo>> notifications = - new HashMap<PrintServiceClient, List<PrintJobInfo>>(); + private void removeUser(int removedUserId) { synchronized (mLock) { - for (PrintServiceClient service : mServices.values()) { - List<PrintJobInfo> printJobs = mSpooler.getPrintJobs( - service.mComponentName, PrintJobInfo.STATE_QUEUED, - PrintManager.APP_ID_ANY, service.mUserId); - notifications.put(service, printJobs); + UserState userState = mUserStates.get(removedUserId); + if (userState != null) { + userState.destroyLocked(); + mUserStates.remove(removedUserId); } } - if (notifications.isEmpty()) { - return false; + } + + private int resolveCallingAppEnforcingPermissions(int appId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == 0 || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return appId; } - for (Map.Entry<PrintServiceClient, List<PrintJobInfo>> notification - : notifications.entrySet()) { - PrintServiceClient service = notification.getKey(); - List<PrintJobInfo> printJobs = notification.getValue(); - final int printJobIdCount = printJobs.size(); - for (int i = 0; i < printJobIdCount; i++) { - service.notifyPrintJobQueued(printJobs.get(i)); - } + final int callingAppId = UserHandle.getAppId(callingUid); + if (appId == callingAppId) { + return appId; + } + if (mContext.checkCallingPermission(Manifest.permission.ACCESS_ALL_PRINT_JOBS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Call from app " + callingAppId + " as app " + + appId + " without permission ACCESS_ALL_PRINT_JOBS"); } - return true; + return appId; } - private int resolveCallingUserEnforcingPermissionsIdLocked(int userId) { + private int resolveCallingUserEnforcingPermissions(int userId) { final int callingUid = Binder.getCallingUid(); if (callingUid == 0 || callingUid == Process.SYSTEM_UID || callingUid == Process.SHELL_UID) { @@ -524,8 +355,8 @@ public final class PrintManagerService extends IPrintManager.Stub { return callingUserId; } throw new SecurityException("Call from user " + callingUserId + " as user " - + userId + " without permission INTERACT_ACROSS_USERS or " - + "INTERACT_ACROSS_USERS_FULL not allowed."); + + userId + " without permission INTERACT_ACROSS_USERS or " + + "INTERACT_ACROSS_USERS_FULL not allowed."); } if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { return mCurrentUserId; @@ -533,257 +364,4 @@ public final class PrintManagerService extends IPrintManager.Stub { throw new IllegalArgumentException("Calling user can be changed to only " + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); } - - private int resolveCallingAppEnforcingPermissionsLocked(int appId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid == 0 || callingUid == Process.SYSTEM_UID - || callingUid == Process.SHELL_UID) { - return appId; - } - final int callingAppId = UserHandle.getAppId(callingUid); - if (appId == callingAppId) { - return appId; - } - if (mContext.checkCallingPermission(Manifest.permission.ACCESS_ALL_PRINT_JOBS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Call from app " + callingAppId + " as app " - + appId + " without permission INTERACT_ACROSS_APPS"); - } - return appId; - } - - private final class PrintServiceClient extends IPrintServiceClient.Stub - implements ServiceConnection, DeathRecipient { - - private final ComponentName mComponentName; - - private final Intent mIntent; - - private final int mUserId; - - private IPrintService mInterface; - - private boolean mBinding; - - private boolean mWasConnectedAndDied; - - public PrintServiceClient(ComponentName componentName, int userId) { - mComponentName = componentName; - mIntent = new Intent().setComponent(mComponentName); - mUserId = userId; - } - - @Override - public List<PrintJobInfo> getPrintJobs() { - return mSpooler.getPrintJobs(mComponentName, PrintJobInfo.STATE_ANY, - PrintManager.APP_ID_ANY, mUserId); - } - - @Override - public PrintJobInfo getPrintJob(int printJobId) { - return mSpooler.getPrintJobInfo(printJobId, - PrintManager.APP_ID_ANY, mUserId); - } - - @Override - public boolean setPrintJobState(int printJobId, int state) { - return mSpooler.setPrintJobState(printJobId, state, mUserId); - } - - @Override - public boolean setPrintJobTag(int printJobId, String tag) { - return mSpooler.setPrintJobTag(printJobId, tag, mUserId); - } - - @Override - public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { - mSpooler.writePrintJobData(fd, printJobId, mUserId); - } - - @Override - public void addDiscoveredPrinters(List<PrinterInfo> printers) { - throwIfPrinterIdsForPrinterInfoTampered(printers); - synchronized (mLock) { - if (mPrinterDiscoveryObserver != null) { - try { - mPrinterDiscoveryObserver.addDiscoveredPrinters(printers); - } catch (RemoteException re) { - /* ignore */ - } - } - } - } - - @Override - public void removeDiscoveredPrinters(List<PrinterId> printerIds) { - throwIfPrinterIdsTampered(printerIds); - synchronized (mLock) { - if (mPrinterDiscoveryObserver != null) { - try { - mPrinterDiscoveryObserver.removeDiscoveredPrinters(printerIds); - } catch (RemoteException re) { - /* ignore */ - } - } - } - } - - public void requestCancelPrintJob(PrintJobInfo printJob) { - synchronized (mLock) { - try { - mInterface.requestCancelPrintJob(printJob); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error canceling pring job!", re); - } - } - } - - public void notifyPrintJobQueued(PrintJobInfo printJob) { - IPrintService service = mInterface; - if (service != null) { - try { - service.onPrintJobQueued(printJob); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - public void startPrinterDiscovery() { - IPrintService service = mInterface; - if (service != null) { - try { - service.startPrinterDiscovery(); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - public void stopPrintersDiscovery() { - IPrintService service = mInterface; - if (service != null) { - try { - service.stopPrinterDiscovery(); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - public void ensureBoundLocked() { - if (mBinding) { - return; - } - if (mInterface == null) { - mBinding = true; - mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE, new UserHandle(mUserId)); - } - } - - public void ensureUnboundLocked() { - if (mBinding) { - mBinding = false; - return; - } - if (mInterface != null) { - mContext.unbindService(this); - destroyLocked(); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mInterface = IPrintService.Stub.asInterface(service); - mServices.put(mComponentName, this); - try { - mInterface.asBinder().linkToDeath(this, 0); - } catch (RemoteException re) { - destroyLocked(); - return; - } - if (mUserId != mCurrentUserId) { - destroyLocked(); - return; - } - if (mBinding || mWasConnectedAndDied) { - mBinding = false; - mWasConnectedAndDied = false; - onUserStateChangedLocked(); - try { - mInterface.setClient(this); - } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error while setting client for service: " - + service, re); - } - } else { - destroyLocked(); - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - /* do nothing - #binderDied takes care */ - } - - @Override - public void binderDied() { - synchronized (mLock) { - if (isConnectedLocked()) { - mWasConnectedAndDied = true; - } - destroyLocked(); - } - } - - private void destroyLocked() { - if (mServices.remove(mComponentName) == null) { - return; - } - if (isConnectedLocked()) { - try { - mInterface.asBinder().unlinkToDeath(this, 0); - } catch (NoSuchElementException nse) { - /* ignore */ - } - try { - mInterface.setClient(null); - } catch (RemoteException re) { - /* ignore */ - } - mInterface = null; - } - mBinding = false; - } - - private boolean isConnectedLocked() { - return (mInterface != null); - } - - private void throwIfPrinterIdsForPrinterInfoTampered(List<PrinterInfo> printerInfos) { - final int printerInfoCount = printerInfos.size(); - for (int i = 0; i < printerInfoCount; i++) { - PrinterId printerId = printerInfos.get(i).getId(); - throwIfPrinterIdTampered(printerId); - } - } - - private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) { - final int printerIdCount = printerIds.size(); - for (int i = 0; i < printerIdCount; i++) { - PrinterId printerId = printerIds.get(i); - throwIfPrinterIdTampered(printerId); - } - } - - private void throwIfPrinterIdTampered(PrinterId printerId) { - if (printerId == null || printerId.getServiceComponentName() == null - || !printerId.getServiceComponentName().equals(mComponentName)) { - throw new IllegalArgumentException("Invalid printer id: " + printerId); - } - } - } } diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java new file mode 100644 index 000000000000..b9e0280bed82 --- /dev/null +++ b/services/java/com/android/server/print/RemotePrintService.java @@ -0,0 +1,466 @@ +/* + * 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 com.android.server.print; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.IPrintService; +import android.printservice.IPrintServiceClient; +import android.util.Slog; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a remote print service. It abstracts away the binding + * 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 { + + private static final String LOG_TAG = "RemotePrintService"; + + private static final boolean DEBUG = true; + + private final Context mContext; + + private final ComponentName mComponentName; + + private final Intent mIntent; + + private final RemotePrintSpooler mSpooler; + + private final int mUserId; + + private final List<Runnable> mPendingCommands = new ArrayList<Runnable>(); + + private final ServiceConnection mServiceConnection = new RemoteServiceConneciton(); + + private final RemotePrintServiceClient mPrintServiceClient; + + private final Handler mHandler; + + private IPrintService mPrintService; + + private boolean mBinding; + + private boolean mDestroyed; + + public RemotePrintService(Context context, ComponentName componentName, int userId, + RemotePrintSpooler spooler) { + mContext = context; + mComponentName = componentName; + mIntent = new Intent().setComponent(mComponentName); + mUserId = userId; + mSpooler = spooler; + mHandler = new MyHandler(context.getMainLooper()); + mPrintServiceClient = new RemotePrintServiceClient(this); + } + + public void destroy() { + mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY); + } + + private void handleDestroy() { + throwIfDestroyed(); + ensureUnbound(); + mDestroyed = true; + } + + public void onAllPrintJobsHandled() { + mHandler.sendEmptyMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED); + } + + private void handleOnAllPrintJobsHandled() { + throwIfDestroyed(); + if (isBound()) { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled"); + } + // If bound and all the work is completed, then unbind. + ensureUnbound(); + } + } + + public void onRequestCancelPrintJob(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_REQUEST_CANCEL_PRINT_JOB, + printJob).sendToTarget(); + } + + private void handleOnRequestCancelPrintJob(final PrintJobInfo printJob) { + throwIfDestroyed(); + // If we are not bound, then we have no print jobs to handle + // which means that there are no print jobs to be cancelled. + if (isBound()) { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnRequestCancelPrintJob()"); + } + try { + mPrintService.requestCancelPrintJob(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error canceling pring job.", re); + } + } + } + + public void onPrintJobQueued(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED, + printJob).sendToTarget(); + } + + private void handleOnPrintJobQueued(final PrintJobInfo printJob) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnPrintJobQueued(printJob); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnPrintJobQueued()"); + } + try { + mPrintService.onPrintJobQueued(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error announcing queued pring job.", re); + } + } + } + + public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) { + mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, observer).sendToTarget(); + } + + private void handleOnStartPrinterDiscovery(final IPrinterDiscoveryObserver observer) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnStartPrinterDiscovery(observer); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onStartPrinterDiscovery()"); + } + try { + mPrintService.startPrinterDiscovery(observer); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re); + } + } + } + + public void onStopPrinterDiscovery() { + mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY); + } + + private void handleStopPrinterDiscovery() { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStopPrinterDiscovery(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onStopPrinterDiscovery()"); + } + try { + mPrintService.stopPrinterDiscovery(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error announcing stop printer dicovery.", re); + } + } + } + + private boolean isBound() { + return mPrintService != null; + } + + private void ensureBound() { + if (isBound() || mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); + } + mBinding = true; + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE, new UserHandle(mUserId)); + } + + private void ensureUnbound() { + if (!isBound() && !mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); + } + mBinding = false; + mPendingCommands.clear(); + if (isBound()) { + try { + mPrintService.setClient(null); + } catch (RemoteException re) { + /* ignore */ + } + mPrintService = null; + mContext.unbindService(mServiceConnection); + } + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed service"); + } + } + + private class RemoteServiceConneciton implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (mDestroyed || !mBinding) { + return; + } + mBinding = false; + mPrintService = IPrintService.Stub.asInterface(service); + try { + mPrintService.setClient(mPrintServiceClient); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting client for: " + service, re); + handleDestroy(); + return; + } + final int pendingCommandCount = mPendingCommands.size(); + for (int i = 0; i < pendingCommandCount; i++) { + Runnable pendingCommand = mPendingCommands.get(i); + pendingCommand.run(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBinding = true; + } + } + + private final class MyHandler extends Handler { + public static final int MSG_ALL_PRINT_JOBS_HANDLED = 1; + public static final int MSG_REQUEST_CANCEL_PRINT_JOB = 2; + public static final int MSG_PRINT_JOB_QUEUED = 3; + 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 MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_ALL_PRINT_JOBS_HANDLED: { + handleOnAllPrintJobsHandled(); + } break; + + case MSG_REQUEST_CANCEL_PRINT_JOB: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleOnRequestCancelPrintJob(printJob); + } break; + + case MSG_PRINT_JOB_QUEUED: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleOnPrintJobQueued(printJob); + } break; + + case MSG_START_PRINTER_DISCOVERY: { + IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) message.obj; + handleOnStartPrinterDiscovery(new SecurePrinterDiscoveryObserver( + mComponentName, observer)); + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + handleStopPrinterDiscovery(); + } break; + + case MSG_DESTROY: { + handleDestroy(); + } break; + } + } + } + + private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub { + private final WeakReference<RemotePrintService> mWeakService; + + public RemotePrintServiceClient(RemotePrintService service) { + mWeakService = new WeakReference<RemotePrintService>(service); + } + + @Override + public List<PrintJobInfo> getPrintJobInfos() { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfos(service.mComponentName, + PrintJobInfo.STATE_ANY, PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public PrintJobInfo getPrintJobInfo(int printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfo(printJobId, + PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public boolean setPrintJobState(int printJobId, int state) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobState(printJobId, state); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public boolean setPrintJobTag(int printJobId, String tag) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobTag(printJobId, tag); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + service.mSpooler.writePrintJobData(fd, printJobId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + } + + private static final class SecurePrinterDiscoveryObserver + extends IPrinterDiscoveryObserver.Stub { + private final ComponentName mComponentName; + + private final IPrinterDiscoveryObserver mDecoratedObsever; + + public SecurePrinterDiscoveryObserver(ComponentName componentName, + IPrinterDiscoveryObserver observer) { + mComponentName = componentName; + mDecoratedObsever = observer; + } + + @Override + public void addDiscoveredPrinters(List<PrinterInfo> printers) { + throwIfPrinterIdsForPrinterInfoTampered(printers); + try { + mDecoratedObsever.addDiscoveredPrinters(printers); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error delegating to addDiscoveredPrinters", re); + } + } + + @Override + public void removeDiscoveredPrinters(List<PrinterId> printerIds) { + throwIfPrinterIdsTampered(printerIds); + try { + mDecoratedObsever.removeDiscoveredPrinters(printerIds); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error delegating to removeDiscoveredPrinters", re); + } + } + + private void throwIfPrinterIdsForPrinterInfoTampered( + List<PrinterInfo> printerInfos) { + final int printerInfoCount = printerInfos.size(); + for (int i = 0; i < printerInfoCount; i++) { + PrinterId printerId = printerInfos.get(i).getId(); + throwIfPrinterIdTampered(printerId); + } + } + + private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) { + final int printerIdCount = printerIds.size(); + for (int i = 0; i < printerIdCount; i++) { + PrinterId printerId = printerIds.get(i); + throwIfPrinterIdTampered(printerId); + } + } + + private void throwIfPrinterIdTampered(PrinterId printerId) { + if (printerId == null || printerId.getService() == null + || !printerId.getService().equals(mComponentName)) { + throw new IllegalArgumentException("Invalid printer id: " + printerId); + } + } + } +} diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java new file mode 100644 index 000000000000..bf2c8e73af6c --- /dev/null +++ b/services/java/com/android/server/print/RemotePrintSpooler.java @@ -0,0 +1,613 @@ +/* + * 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 com.android.server.print; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.print.IPrintClient; +import android.print.IPrintDocumentAdapter; +import android.print.IPrintSpooler; +import android.print.IPrintSpoolerCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintAttributes; +import android.print.PrintJobInfo; +import android.util.Slog; +import android.util.TimedRemoteCaller; + +import libcore.io.IoUtils; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.TimeoutException; + +/** + * This represents the remote print spooler as a local object to the + * PrintManagerSerivce. It is responsible to connecting to the remote + * spooler if needed, to make the timed remote calls, to handle + * remote exceptions, and to bind/unbind to the remote instance as + * needed. + */ +final class RemotePrintSpooler { + + private static final String LOG_TAG = "RemotePrintSpooler"; + + private static final boolean DEBUG = true; + + private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000; + + private final Object mLock = new Object(); + + private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller(); + + private final CreatePrintJobCaller mCreatePrintJobCaller = new CreatePrintJobCaller(); + + private final CancelPrintJobCaller mCancelPrintJobCaller = new CancelPrintJobCaller(); + + private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller(); + + private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller(); + + private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller(); + + private final ServiceConnection mServiceConnection = new MyServiceConnection(); + + private final Context mContext; + + private final UserHandle mUserHandle; + + private final PrintSpoolerClient mClient; + + private final Intent mIntent; + + private final PrintSpoolerCallbacks mCallbacks; + + private IPrintSpooler mRemoteInstance; + + private boolean mDestroyed; + + public static interface PrintSpoolerCallbacks { + public void onPrintJobQueued(PrintJobInfo printJob); + public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer); + public void onStopPrinterDiscovery(); + public void onAllPrintJobsForServiceHandled(ComponentName printService); + } + + public RemotePrintSpooler(Context context, int userId, + PrintSpoolerCallbacks callbacks) { + mContext = context; + mUserHandle = new UserHandle(userId); + mCallbacks = callbacks; + mClient = new PrintSpoolerClient(this); + mIntent = new Intent(); + mIntent.setComponent(new ComponentName("com.android.printspooler", + "com.android.printspooler.PrintSpoolerService")); + } + + public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, + int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()"); + } + try { + return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(), + componentName, state, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print jobs.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print jobs.", te); + } + return null; + } + + public final PrintJobInfo createPrintJob(String printJobName, IPrintClient client, + IPrintDocumentAdapter documentAdapter, PrintAttributes attributes, int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()"); + } + try { + return mCreatePrintJobCaller.createPrintJob(getRemoteInstanceLazy(), + printJobName, client, documentAdapter, attributes, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error creating print job.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error creating print job.", te); + } + return null; + } + + public final boolean cancelPrintJob(int printJobId, int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()"); + } + try { + return mCancelPrintJobCaller.cancelPrintJob(getRemoteInstanceLazy(), + printJobId, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error canceling print job.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error canceling print job.", te); + } + return false; + } + + public final void writePrintJobData(ParcelFileDescriptor fd, int printJobId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()"); + } + try { + getRemoteInstanceLazy().writePrintJobData(fd, printJobId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error writing print job data.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error writing print job data.", te); + } finally { + // We passed the file descriptor across and now the other + // side is responsible to close it, so close the local copy. + IoUtils.closeQuietly(fd); + } + } + + public final PrintJobInfo getPrintJobInfo(int printJobId, int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()"); + } + try { + return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(), + printJobId, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print job info.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print job info.", te); + } + return null; + } + + public final boolean setPrintJobState(int printJobId, int state) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()"); + } + try { + return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(), + printJobId, state); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job state.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job state.", te); + } + return false; + } + + public final boolean setPrintJobTag(int printJobId, String tag) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()"); + } + try { + return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(), + printJobId, tag); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job tag.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job tag.", te); + } + return false; + } + + public final void notifyClientForActivteJobs() { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + + "] notifyClientForActivteJobs()"); + } + try { + getRemoteInstanceLazy().notifyClientForActivteJobs(); + } catch (RemoteException re) { + 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); + } + } + + public final void destroy() { + throwIfCalledOnMainThread(); + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()"); + } + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + mDestroyed = true; + } + } + + private void onAllPrintJobsHandled() { + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + } + } + + private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException { + synchronized (mLock) { + if (mRemoteInstance != null) { + return mRemoteInstance; + } + bindLocked(); + return mRemoteInstance; + } + } + + private void bindLocked() throws TimeoutException { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked()"); + } + + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT, + mUserHandle); + + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + if (mRemoteInstance != null) { + break; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis; + if (remainingMillis <= 0) { + throw new TimeoutException("Cannot get spooler!"); + } + try { + mLock.wait(remainingMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + + private void unbindLocked() { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()"); + } + clearClientLocked(); + mRemoteInstance = null; + mContext.unbindService(mServiceConnection); + } + + private void setClientLocked() { + try { + mRemoteInstance.setClient(mClient); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error setting print spooler client", re); + } + } + + private void clearClientLocked() { + try { + mRemoteInstance.setClient(null); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error clearing print spooler client", re); + } + + } + + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed instance."); + } + } + + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + private final class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mRemoteInstance = IPrintSpooler.Stub.asInterface(service); + setClientLocked(); + mLock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + clearClientLocked(); + mRemoteInstance = null; + } + } + } + + private static final class GetPrintJobInfosCaller + extends TimedRemoteCaller<List<PrintJobInfo>> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfosCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) { + onRemoteMethodResult(printJobs, sequence); + } + }; + } + + public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target, + ComponentName componentName, int state, int appId) + throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfos(mCallback, componentName, state, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class CreatePrintJobCaller extends TimedRemoteCaller<PrintJobInfo> { + private final IPrintSpoolerCallbacks mCallback; + + public CreatePrintJobCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) { + onRemoteMethodResult(printJob, sequence); + } + }; + } + + public PrintJobInfo createPrintJob(IPrintSpooler target, String printJobName, + IPrintClient client, IPrintDocumentAdapter documentAdapter, + PrintAttributes attributes, int appId) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.createPrintJob(printJobName, client, documentAdapter, attributes, + mCallback, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class CancelPrintJobCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public CancelPrintJobCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onCancelPrintJobResult(boolean canceled, int sequence) { + onRemoteMethodResult(canceled, sequence); + } + }; + } + + public boolean cancelPrintJob(IPrintSpooler target, int printJobId, + int appId) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.cancelPrintJob(printJobId, mCallback, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfoCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + onRemoteMethodResult(printJob, sequence); + } + }; + } + + public PrintJobInfo getPrintJobInfo(IPrintSpooler target, int printJobId, + int appId) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfo(printJobId, mCallback, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobStateCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobStateResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobState(IPrintSpooler target, int printJobId, + int status) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobState(printJobId, status, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobTagCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobTag(IPrintSpooler target, int printJobId, + String tag) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobTag(printJobId, tag, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static abstract class BasePrintSpoolerServiceCallbacks + extends IPrintSpoolerCallbacks.Stub { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) { + /* do nothing */ + } + + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + /* do nothing */ + } + + @Override + public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) { + /* do nothing */ + } + + @Override + public void onCancelPrintJobResult(boolean canceled, int sequence) { + /* do nothing */ + } + + @Override + public void onSetPrintJobStateResult(boolean success, int sequece) { + /* do nothing */ + } + + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + /* do nothing */ + } + } + + private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub { + + private final WeakReference<RemotePrintSpooler> mWeakSpooler; + + public PrintSpoolerClient(RemotePrintSpooler spooler) { + mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onPrintJobQueued(printJob); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsForServiceHandled(ComponentName printService) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsHandled() { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.onAllPrintJobsHandled(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onStartPrinterDiscovery(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onStopPrinterDiscovery() throws RemoteException { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onStopPrinterDiscovery(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + } +} diff --git a/services/java/com/android/server/print/RemoteSpooler.java b/services/java/com/android/server/print/RemoteSpooler.java deleted file mode 100644 index fef581818183..000000000000 --- a/services/java/com/android/server/print/RemoteSpooler.java +++ /dev/null @@ -1,416 +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 com.android.server.print; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Binder; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.IBinder.DeathRecipient; -import android.print.IPrintAdapter; -import android.print.IPrintClient; -import android.print.IPrintSpoolerService; -import android.print.IPrintSpoolerServiceCallbacks; -import android.print.PrintAttributes; -import android.print.PrintJobInfo; -import android.util.Slog; -import android.util.TimedRemoteCaller; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This represents the remote print spooler as a local object to the - * PrintManagerSerivce. It is responsible to connecting to the remove - * spooler if needed, to make the timed out remote calls, and to handle - * remove exceptions. - */ -final class RemoteSpooler implements ServiceConnection, DeathRecipient { - - private static final String LOG_TAG = "Spooler"; - - private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000; - - private final Object mLock = new Object(); - - private final Context mContext; - - private final Intent mIntent; - - private final GetPrintJobsCaller mGetPrintJobsCaller = new GetPrintJobsCaller(); - - private final CreatePrintJobCaller mCreatePrintJobCaller = new CreatePrintJobCaller(); - - private final CancelPrintJobCaller mCancelPrintJobCaller = new CancelPrintJobCaller(); - - private final GetPrintJobCaller mGetPrintJobCaller = new GetPrintJobCaller(); - - private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller(); - - private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller(); - - private IPrintSpoolerService mRemoteInterface; - - private int mUserId = UserHandle.USER_NULL; - - public RemoteSpooler(Context context) { - mContext = context; - mIntent = new Intent(); - mIntent.setComponent(new ComponentName("com.android.printspooler", - "com.android.printspooler.PrintSpoolerService")); - } - - public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId, - int userId) { - try { - return mGetPrintJobsCaller.getPrintJobs(getRemoteInstance(userId), - componentName, state, appId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error getting print jobs!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error getting print jobs!", te); - } - return null; - } - - public PrintJobInfo createPrintJob(String printJobName, IPrintClient client, - IPrintAdapter printAdapter, PrintAttributes attributes, int appId, int userId) { - try { - return mCreatePrintJobCaller.createPrintJob(getRemoteInstance(userId), - printJobName, client, printAdapter, attributes, appId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error creating print job!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error creating print job!", te); - } - return null; - } - - public boolean cancelPrintJob(int printJobId, int appId, int userId) { - try { - return mCancelPrintJobCaller.cancelPrintJob(getRemoteInstance(userId), - printJobId, appId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error canceling print job!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error canceling print job!", te); - } - return false; - } - - public void writePrintJobData(ParcelFileDescriptor fd, int printJobId, int userId) { - try { - getRemoteInstance(userId).writePrintJobData(fd, printJobId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error writing print job data!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error writing print job data!", te); - } finally { - // We passed the file descriptor across and now the other - // side is responsible to close it, so close the local copy. - try { - fd.close(); - } catch (IOException ioe) { - /* ignore */ - } - } - } - - public PrintJobInfo getPrintJobInfo(int printJobId, int appId, int userId) { - try { - return mGetPrintJobCaller.getPrintJobInfo(getRemoteInstance(userId), - printJobId, appId); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error getting print job!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error getting print job!", te); - } - return null; - } - - public boolean setPrintJobState(int printJobId, int state, int userId) { - try { - return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstance(userId), - printJobId, state); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error setting print job status!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error setting print job status!", te); - } - return false; - } - - public boolean setPrintJobTag(int printJobId, String tag, int userId) { - try { - return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstance(userId), - printJobId, tag); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error setting print job tag!", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error setting print job tag!", te); - } - return false; - } - - @Override - public void onServiceDisconnected(ComponentName name) { - binderDied(); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - try { - service.linkToDeath(this, 0); - mRemoteInterface = IPrintSpoolerService.Stub.asInterface(service); - } catch (RemoteException re) { - /* ignore */ - } - } - } - - private IPrintSpoolerService getRemoteInstance(int userId) throws TimeoutException { - synchronized (mLock) { - if (mRemoteInterface != null && mUserId == userId) { - return mRemoteInterface; - } - - final long identity = Binder.clearCallingIdentity(); - try { - if (mUserId != UserHandle.USER_NULL && mUserId != userId) { - unbind(); - } - - mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT, - UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(identity); - } - - final long startMillis = SystemClock.uptimeMillis(); - while (true) { - if (mRemoteInterface != null) { - break; - } - final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; - final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis; - if (remainingMillis <= 0) { - throw new TimeoutException("Cannot get spooler!"); - } - try { - mLock.wait(remainingMillis); - } catch (InterruptedException ie) { - /* ignore */ - } - } - - mUserId = userId; - - return mRemoteInterface; - } - } - - public void unbind() { - synchronized (mLock) { - if (mRemoteInterface != null) { - mContext.unbindService(this); - mRemoteInterface = null; - mUserId = UserHandle.USER_NULL; - } - } - } - - @Override - public void binderDied() { - synchronized (mLock) { - if (mRemoteInterface != null) { - mRemoteInterface.asBinder().unlinkToDeath(this, 0); - mRemoteInterface = null; - } - } - } - - private final class GetPrintJobsCaller extends TimedRemoteCaller<List<PrintJobInfo>> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public GetPrintJobsCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onGetPrintJobsResult(List<PrintJobInfo> printJobs, int sequence) { - onRemoteMethodResult(printJobs, sequence); - } - }; - } - - public List<PrintJobInfo> getPrintJobs(IPrintSpoolerService target, - ComponentName componentName, int state, int appId) - throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.getPrintJobs(mCallback, componentName, state, appId, sequence); - return getResultTimed(sequence); - } - } - - private final class CreatePrintJobCaller extends TimedRemoteCaller<PrintJobInfo> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public CreatePrintJobCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) { - onRemoteMethodResult(printJob, sequence); - } - }; - } - - public PrintJobInfo createPrintJob(IPrintSpoolerService target, String printJobName, - IPrintClient client, IPrintAdapter printAdapter, PrintAttributes attributes, - int appId) throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.createPrintJob(printJobName, client, printAdapter, attributes, - mCallback, appId, sequence); - return getResultTimed(sequence); - } - } - - private final class CancelPrintJobCaller extends TimedRemoteCaller<Boolean> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public CancelPrintJobCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onCancelPrintJobResult(boolean canceled, int sequence) { - onRemoteMethodResult(canceled, sequence); - } - }; - } - - public boolean cancelPrintJob(IPrintSpoolerService target, int printJobId, - int appId) throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.cancelPrintJob(printJobId, mCallback, appId, sequence); - return getResultTimed(sequence); - } - } - - private final class GetPrintJobCaller extends TimedRemoteCaller<PrintJobInfo> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public GetPrintJobCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { - onRemoteMethodResult(printJob, sequence); - } - }; - } - - public PrintJobInfo getPrintJobInfo(IPrintSpoolerService target, int printJobId, - int appId) throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.getPrintJob(printJobId, mCallback, appId, sequence); - return getResultTimed(sequence); - } - } - - private final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public SetPrintJobStateCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onSetPrintJobStateResult(boolean success, int sequence) { - onRemoteMethodResult(success, sequence); - } - }; - } - - public boolean setPrintJobState(IPrintSpoolerService target, int printJobId, - int status) throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.setPrintJobState(printJobId, status, mCallback, sequence); - return getResultTimed(sequence); - } - } - - private final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public SetPrintJobTagCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onSetPrintJobTagResult(boolean success, int sequence) { - onRemoteMethodResult(success, sequence); - } - }; - } - - public boolean setPrintJobTag(IPrintSpoolerService target, int printJobId, - String tag) throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.setPrintJobTag(printJobId, tag, mCallback, sequence); - return getResultTimed(sequence); - } - } - - private abstract class BasePrintSpoolerServiceCallbacks - extends IPrintSpoolerServiceCallbacks.Stub { - @Override - public void onGetPrintJobsResult(List<PrintJobInfo> printJobIds, int sequence) { - /** do nothing */ - } - - @Override - public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { - /** do nothing */ - } - - @Override - public void onCreatePrintJobResult(PrintJobInfo printJob, int sequence) { - /** do nothing */ - } - - @Override - public void onCancelPrintJobResult(boolean canceled, int sequence) { - /** do nothing */ - } - - @Override - public void onSetPrintJobStateResult(boolean success, int sequece) { - /** do nothing */ - } - - @Override - public void onSetPrintJobTagResult(boolean success, int sequence) { - /** do nothing */ - } - } -}
\ No newline at end of file diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java new file mode 100644 index 000000000000..5cef4d3ceae7 --- /dev/null +++ b/services/java/com/android/server/print/UserState.java @@ -0,0 +1,274 @@ +/* + * 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 com.android.server.print; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintJobInfo; +import android.printservice.PrintServiceInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.Slog; + +import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents the print state for a user. + */ +final class UserState implements PrintSpoolerCallbacks { + + private static final String LOG_TAG = "UserState"; + + private static final char COMPONENT_NAME_SEPARATOR = ':'; + + private final SimpleStringSplitter mStringColonSplitter = + new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); + + private final Intent mQueryIntent = + new Intent(android.printservice.PrintService.SERVICE_INTERFACE); + + private final Map<ComponentName, RemotePrintService> mActiveServices = + new HashMap<ComponentName, RemotePrintService>(); + + private final List<PrintServiceInfo> mInstalledServices = + new ArrayList<PrintServiceInfo>(); + + private final Set<ComponentName> mEnabledServices = + new HashSet<ComponentName>(); + + private final Object mLock; + + private final Context mContext; + + private final int mUserId; + + private final RemotePrintSpooler mSpooler; + + private boolean mDestroyed; + + public UserState(Context context, int userId, Object lock) { + mContext = context; + mUserId = userId; + mLock = lock; + mSpooler = new RemotePrintSpooler(context, userId, this); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + final RemotePrintService service; + synchronized (mLock) { + throwIfDestroyedLocked(); + ComponentName printServiceName = printJob.getPrinterId().getService(); + service = mActiveServices.get(printServiceName); + } + if (service != null) { + service.onPrintJobQueued(printJob); + } + } + + @Override + public void onAllPrintJobsForServiceHandled(ComponentName printService) { + final RemotePrintService service; + synchronized (mLock) { + throwIfDestroyedLocked(); + service = mActiveServices.get(printService); + } + if (service != null) { + service.onAllPrintJobsHandled(); + } + } + + @Override + public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) { + final List<RemotePrintService> services; + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mActiveServices.isEmpty()) { + return; + } + services = new ArrayList<RemotePrintService>(mActiveServices.values()); + } + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.onStartPrinterDiscovery(observer); + } + } + + @Override + public void onStopPrinterDiscovery() { + final List<RemotePrintService> services; + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mActiveServices.isEmpty()) { + return; + } + services = new ArrayList<RemotePrintService>(mActiveServices.values()); + } + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.onStopPrinterDiscovery(); + } + } + + public void updateIfNeededLocked() { + throwIfDestroyedLocked(); + if (readConfigurationLocked()) { + onConfigurationChangedLocked(); + } + } + + public RemotePrintSpooler getSpoolerLocked() { + throwIfDestroyedLocked(); + return mSpooler; + } + + public Map<ComponentName, RemotePrintService> getActiveServices() { + synchronized(mLock) { + throwIfDestroyedLocked(); + return mActiveServices; + } + } + + public Set<ComponentName> getEnabledServices() { + synchronized(mLock) { + throwIfDestroyedLocked(); + return mEnabledServices; + } + } + + public void destroyLocked() { + throwIfDestroyedLocked(); + mSpooler.destroy(); + for (RemotePrintService service : mActiveServices.values()) { + service.destroy(); + } + mActiveServices.clear(); + mInstalledServices.clear(); + mEnabledServices.clear(); + mDestroyed = true; + } + + private boolean readConfigurationLocked() { + boolean somethingChanged = false; + somethingChanged |= readInstalledPrintServicesLocked(); + somethingChanged |= readEnabledPrintServicesLocked(); + return somethingChanged; + } + + private boolean readInstalledPrintServicesLocked() { + Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>(); + + List<ResolveInfo> installedServices = mContext.getPackageManager() + .queryIntentServicesAsUser(mQueryIntent, PackageManager.GET_SERVICES + | PackageManager.GET_META_DATA, mUserId); + + final int installedCount = installedServices.size(); + for (int i = 0, count = installedCount; i < count; i++) { + ResolveInfo installedService = installedServices.get(i); + if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals( + installedService.serviceInfo.permission)) { + ComponentName serviceName = new ComponentName( + installedService.serviceInfo.packageName, + installedService.serviceInfo.name); + Slog.w(LOG_TAG, "Skipping print service " + + serviceName.flattenToShortString() + + " since it does not require permission " + + android.Manifest.permission.BIND_PRINT_SERVICE); + continue; + } + tempPrintServices.add(PrintServiceInfo.create(installedService, mContext)); + } + + if (!tempPrintServices.equals(mInstalledServices)) { + mInstalledServices.clear(); + mInstalledServices.addAll(tempPrintServices); + return true; + } + + return false; + } + + private boolean readEnabledPrintServicesLocked() { + Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>(); + + String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); + if (!TextUtils.isEmpty(settingValue)) { + TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; + splitter.setString(settingValue); + while (splitter.hasNext()) { + String string = splitter.next(); + if (TextUtils.isEmpty(string)) { + continue; + } + ComponentName componentName = ComponentName.unflattenFromString(string); + if (componentName != null) { + tempEnabledServiceNameSet.add(componentName); + } + } + } + + if (!tempEnabledServiceNameSet.equals(mEnabledServices)) { + mEnabledServices.clear(); + mEnabledServices.addAll(tempEnabledServiceNameSet); + return true; + } + + return false; + } + + private void onConfigurationChangedLocked() { + final int installedCount = mInstalledServices.size(); + for (int i = 0; i < installedCount; i++) { + ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo(); + ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + if (mEnabledServices.contains(serviceName)) { + if (!mActiveServices.containsKey(serviceName)) { + mActiveServices.put(serviceName, new RemotePrintService( + mContext, serviceName, mUserId, mSpooler)); + } + } else { + RemotePrintService service = mActiveServices.remove(serviceName); + if (service != null) { + service.destroy(); + } + } + } + } + + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed instance."); + } + } +} + |