From a00271533f639c8ed36429c663889ac9f654bc72 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Tue, 25 Jun 2013 14:59:53 -0700 Subject: Refactoring of the print sub-system and API clean up. 1. Now a user state has ins own spooler since the spooler app is running per user. The user state registers an observer for the state of the spooler to get information needed to orchestrate unbinding from print serivces that have no work and eventually unbinding from the spooler when all no service has any work. 2. Abstracted a remote print service from the perspective of the system in a class that is transparently managing binding and unbinding to the remote instance. 3. Abstracted the remote print spooler to transparently manage binding and unbinding to the remote instance when there is work and when there is no work, respectively. 4. Cleaned up the print document adapter (ex-PrintAdapter) APIs to enable implementing the all callbacks on a thread of choice. If the document is really small, using the main thread makes sense. Now if an app that does not need the UI state to layout the printed content, it can schedule all the work for allocating resources, laying out, writing, and releasing resources on a dedicated thread. 5. Added info class for the printed document that is now propagated the the print services. A print service gets an instance of a new document class that encapsulates the document info and a method to access the document's data. 6. Added APIs for describing the type of a document to the new document info class. This allows a print service to do smarts based on the doc type. For now we have only photo and document types. 7. Renamed the systemReady method for system services that implement it with different semantics to systemRunning. Such methods assume the the service can run third-party code which is not the same as systemReady. 8. Cleaned up the print job configuration activity. 9. Sigh... code clean up here and there. Factoring out classes to improve readability. Change-Id: I637ba28412793166cbf519273fdf022241159a92 --- Android.mk | 10 +- api/current.txt | 76 ++- core/java/android/print/FileDocumentAdapter.java | 128 ++++ core/java/android/print/ILayoutResultCallback.aidl | 31 + core/java/android/print/IPrintAdapter.aidl | 35 - core/java/android/print/IPrintDocumentAdapter.aidl | 37 ++ core/java/android/print/IPrintManager.aidl | 18 +- core/java/android/print/IPrintResultCallback.aidl | 33 - core/java/android/print/IPrintSpooler.aidl | 52 ++ .../java/android/print/IPrintSpoolerCallbacks.aidl | 36 ++ core/java/android/print/IPrintSpoolerClient.aidl | 35 + core/java/android/print/IPrintSpoolerObserver.aidl | 31 + core/java/android/print/IPrintSpoolerService.aidl | 49 -- .../print/IPrintSpoolerServiceCallbacks.aidl | 36 -- core/java/android/print/IWriteResultCallback.aidl | 31 + core/java/android/print/PrintAdapter.java | 164 ----- core/java/android/print/PrintAdapterInfo.aidl | 19 - core/java/android/print/PrintAdapterInfo.java | 136 ---- core/java/android/print/PrintDocumentAdapter.java | 200 ++++++ core/java/android/print/PrintDocumentInfo.aidl | 19 + core/java/android/print/PrintDocumentInfo.java | 171 +++++ core/java/android/print/PrintFileAdapter.java | 125 ---- core/java/android/print/PrintJob.java | 2 +- core/java/android/print/PrintJobInfo.java | 94 ++- core/java/android/print/PrintManager.java | 239 +++---- core/java/android/print/PrinterId.java | 2 +- core/java/android/print/PrinterInfo.java | 115 ++-- core/java/android/printservice/IPrintService.aidl | 7 +- .../android/printservice/IPrintServiceClient.aidl | 6 +- core/java/android/printservice/PrintDocument.java | 91 +++ core/java/android/printservice/PrintJob.java | 91 +-- core/java/android/printservice/PrintService.java | 182 +++--- .../android/printservice/PrintServiceInfo.java | 15 +- core/res/res/values/strings.xml | 78 +-- .../printspooler/PrintJobConfigActivity.java | 229 +++---- .../src/com/android/printspooler/PrintSpooler.java | 279 ++++++-- .../android/printspooler/PrintSpoolerService.java | 35 +- .../android/printspooler/RemotePrintAdapter.java | 219 ------- .../printspooler/RemotePrintDocumentAdapter.java | 448 +++++++++++++ .../java/com/android/server/AppWidgetService.java | 2 +- .../java/com/android/server/AssetAtlasService.java | 2 +- .../server/CommonTimeManagementService.java | 2 +- .../com/android/server/CountryDetectorService.java | 2 +- .../android/server/InputMethodManagerService.java | 2 +- .../com/android/server/LocationManagerService.java | 2 +- .../android/server/NetworkTimeUpdateService.java | 2 +- services/java/com/android/server/SystemServer.java | 61 +- .../java/com/android/server/TelephonyRegistry.java | 2 +- .../android/server/TextServicesManagerService.java | 2 +- .../android/server/WallpaperManagerService.java | 2 +- .../android/server/dreams/DreamManagerService.java | 2 +- .../android/server/input/InputManagerService.java | 2 +- .../android/server/print/PrintManagerService.java | 720 +++++---------------- .../android/server/print/RemotePrintService.java | 466 +++++++++++++ .../android/server/print/RemotePrintSpooler.java | 613 ++++++++++++++++++ .../com/android/server/print/RemoteSpooler.java | 416 ------------ .../java/com/android/server/print/UserState.java | 274 ++++++++ 57 files changed, 3696 insertions(+), 2482 deletions(-) create mode 100644 core/java/android/print/FileDocumentAdapter.java create mode 100644 core/java/android/print/ILayoutResultCallback.aidl delete mode 100644 core/java/android/print/IPrintAdapter.aidl create mode 100644 core/java/android/print/IPrintDocumentAdapter.aidl delete mode 100644 core/java/android/print/IPrintResultCallback.aidl create mode 100644 core/java/android/print/IPrintSpooler.aidl create mode 100644 core/java/android/print/IPrintSpoolerCallbacks.aidl create mode 100644 core/java/android/print/IPrintSpoolerClient.aidl create mode 100644 core/java/android/print/IPrintSpoolerObserver.aidl delete mode 100644 core/java/android/print/IPrintSpoolerService.aidl delete mode 100644 core/java/android/print/IPrintSpoolerServiceCallbacks.aidl create mode 100644 core/java/android/print/IWriteResultCallback.aidl delete mode 100644 core/java/android/print/PrintAdapter.java delete mode 100644 core/java/android/print/PrintAdapterInfo.aidl delete mode 100644 core/java/android/print/PrintAdapterInfo.java create mode 100644 core/java/android/print/PrintDocumentAdapter.java create mode 100644 core/java/android/print/PrintDocumentInfo.aidl create mode 100644 core/java/android/print/PrintDocumentInfo.java delete mode 100644 core/java/android/print/PrintFileAdapter.java create mode 100644 core/java/android/printservice/PrintDocument.java delete mode 100644 packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java create mode 100644 packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java create mode 100644 services/java/com/android/server/print/RemotePrintService.java create mode 100644 services/java/com/android/server/print/RemotePrintSpooler.java delete mode 100644 services/java/com/android/server/print/RemoteSpooler.java create mode 100644 services/java/com/android/server/print/UserState.java 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 711c87c14b7b..e9c35ef929b5 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, 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); - } - - 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, 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); + } + + 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 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/FileDocumentAdapter.java b/core/java/android/print/FileDocumentAdapter.java new file mode 100644 index 000000000000..d162c19fdb37 --- /dev/null +++ b/core/java/android/print/FileDocumentAdapter.java @@ -0,0 +1,128 @@ +/* + * 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.AsyncTask; +import android.os.CancellationSignal; +import android.os.CancellationSignal.OnCancelListener; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileDescriptor; +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; + +/** + * Adapter for printing files. + */ +final class FileDocumentAdapter extends PrintDocumentAdapter { + + private static final String LOG_TAG = "FileDocumentAdapter"; + + private final File mFile; + + private WriteFileAsyncTask mWriteFileAsyncTask; + + public FileDocumentAdapter(File file) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null!"); + } + mFile = file; + } + + @Override + 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 pages, FileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal, + callback); + mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, + (Void[]) null); + + } + + private static final class WriteFileAsyncTask extends AsyncTask { + + private final File mSource; + + private final FileDescriptor mDestination; + + private final WriteResultCallback mResultCallback; + + private final CancellationSignal mCancellationSignal; + + public WriteFileAsyncTask(File source, FileDescriptor destination, + CancellationSignal cancellationSignal, WriteResultCallback callback) { + mSource = source; + mDestination = destination; + mResultCallback = callback; + mCancellationSignal = cancellationSignal; + mCancellationSignal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + cancel(true); + } + }); + } + + @Override + protected Void doInBackground(Void... params) { + InputStream in = null; + OutputStream out = new FileOutputStream(mDestination); + final byte[] buffer = new byte[8192]; + try { + in = new FileInputStream(mSource); + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + break; + } + out.write(buffer, 0, readByteCount); + } + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing data!", ioe); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + if (!isCancelled()) { + List pages = new ArrayList(); + pages.add(PageRange.ALL_PAGES); + mResultCallback.onWriteFinished(pages); + } else { + 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/IPrintAdapter.aidl deleted file mode 100644 index f3ff8c4d0ce8..000000000000 --- a/core/java/android/print/IPrintAdapter.aidl +++ /dev/null @@ -1,35 +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.ParcelFileDescriptor; -import android.print.IPrintResultCallback; -import android.print.PageRange; -import android.print.PrintAttributes; - -/** - * Interface for communication with the print adapter object. - * - * @hide - */ -oneway interface IPrintAdapter { - void start(); - void printAttributesChanged(in PrintAttributes attributes); - void print(in List pages, in ParcelFileDescriptor fd, - IPrintResultCallback callback); - void finish(); -} diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl new file mode 100644 index 000000000000..36938e3592cc --- /dev/null +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -0,0 +1,37 @@ +/* + * 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.ParcelFileDescriptor; +import android.print.ILayoutResultCallback; +import android.print.IWriteResultCallback; +import android.print.PageRange; +import android.print.PrintAttributes; + +/** + * Interface for communication with the print adapter object. + * + * @hide + */ +oneway interface IPrintDocumentAdapter { + void start(); + void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes, + ILayoutResultCallback callback); + void write(in List 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 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 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/IPrintResultCallback.aidl b/core/java/android/print/IPrintResultCallback.aidl deleted file mode 100644 index 838377ea4a96..000000000000 --- a/core/java/android/print/IPrintResultCallback.aidl +++ /dev/null @@ -1,33 +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.ICancellationSignal; -import android.print.PageRange; -import android.print.PrintAdapterInfo; - -/** - * Callbacks for observing the print progress (writing of printed content) - * of a PrintAdapter. - * - * @hide - */ -oneway interface IPrintResultCallback { - void onPrintStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal); - void onPrintFinished(in List pages); - void onPrintFailed(CharSequence error); -} diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl new file mode 100644 index 000000000000..c55205d0f1ff --- /dev/null +++ b/core/java/android/print/IPrintSpooler.aidl @@ -0,0 +1,52 @@ +/* + * 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.os.ParcelFileDescriptor; +import android.print.IPrintDocumentAdapter; +import android.print.IPrintClient; +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.IPrintSpoolerCallbacks + * + * @hide + */ +oneway interface IPrintSpooler { + void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName, + int state, int appId, int sequence); + void getPrintJobInfo(int printJobId, IPrintSpoolerCallbacks callback, + int appId, int sequence); + 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, IPrintSpoolerCallbacks callback, + int sequence); + void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback, + int sequence); + void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); + void setClient(IPrintSpoolerClient client); + void notifyClientForActivteJobs(); +} diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl new file mode 100644 index 000000000000..7912964efecc --- /dev/null +++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl @@ -0,0 +1,36 @@ +/* + * 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.PrintJobInfo; +import java.util.List; + +/** + * Callbacks for communication with the print spooler service. + * + * @see android.print.IPrintSpoolerService + * + * @hide + */ +oneway interface IPrintSpoolerCallbacks { + void onGetPrintJobInfosResult(in List printJob, int sequence); + void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence); + void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence); + void onCancelPrintJobResult(boolean canceled, int sequence); + void onSetPrintJobStateResult(boolean success, int sequence); + void onSetPrintJobTagResult(boolean success, int sequence); +} 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/IPrintSpoolerService.aidl b/core/java/android/print/IPrintSpoolerService.aidl deleted file mode 100644 index e84d5927fe0c..000000000000 --- a/core/java/android/print/IPrintSpoolerService.aidl +++ /dev/null @@ -1,49 +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.content.ComponentName; -import android.os.ParcelFileDescriptor; -import android.print.IPrintAdapter; -import android.print.IPrintClient; -import android.print.IPrintSpoolerServiceCallbacks; -import android.print.PrinterInfo; -import android.print.PrintAttributes; - -/** - * Interface for communication with the print spooler service. - * - * @see android.print.IPrintSpoolerServiceCallbacks - * - * @hide - */ -oneway interface IPrintSpoolerService { - void getPrintJobs(IPrintSpoolerServiceCallbacks callback, in ComponentName componentName, - int state, int appId, int sequence); - void getPrintJob(int printJobId, IPrintSpoolerServiceCallbacks 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, - int appId, int sequence); - void setPrintJobState(int printJobId, int status, IPrintSpoolerServiceCallbacks callback, - int sequence); - void setPrintJobTag(int printJobId, String tag, IPrintSpoolerServiceCallbacks callback, - int sequence); - void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); -} \ No newline at end of file diff --git a/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl b/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl deleted file mode 100644 index 0c519136bb83..000000000000 --- a/core/java/android/print/IPrintSpoolerServiceCallbacks.aidl +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.print; - -import android.print.PrintJobInfo; -import java.util.List; - -/** - * Callbacks for communication with the print spooler service. - * - * @see android.print.IPrintSpoolerService - * - * @hide - */ -oneway interface IPrintSpoolerServiceCallbacks { - void onGetPrintJobsResult(in List printJob, int sequence); - void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence); - void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence); - void onCancelPrintJobResult(boolean canceled, int sequence); - void onSetPrintJobStateResult(boolean success, int sequence); - void onSetPrintJobTagResult(boolean success, int sequence); -} diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl new file mode 100644 index 000000000000..d5428b145aff --- /dev/null +++ b/core/java/android/print/IWriteResultCallback.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.PageRange; + +/** + * Callback for observing the result of android.print.DocuemntAdapter#onWrite. + * + * @hide + */ +oneway interface IWriteResultCallback { + void onWriteStarted(ICancellationSignal cancellationSignal); + void onWriteFinished(in List 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. - * - *

Lifecycle

- *

- *

    - *
  • - * You will receive a call on {@link #onStart()} when printing starts. - * This callback can be used to allocate resources. - *
  • - *
  • - * 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. - *
  • - *
  • - * 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. - *
  • - *
  • - * Finally, you will receive a call on {@link #onFinish()} right after printing. - * You can use this callback to release resources. - *
  • - *
  • - * 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}. - *
  • - *
- *

- *

- */ -public abstract class PrintAdapter { - - /** - * Called when printing started. You can use this callback to - * allocate resources. - *

- * Note: Invoked on the main thread. - *

- */ - 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. - *

- * Note: Invoked on the main thread. - *

- * - * @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 not - * close the file descriptor instead you have to invoke {@link PrintResultCallback - * #onPrintFinished()} or {@link PrintResultCallback#onPrintFailed(CharSequence)}. - *

- * Note: 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. - *

- *

- * Note: Invoked on the main thread. - *

- * - * @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 pages, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback progressListener); - - /** - * Called when printing finished. You can use this callback to release - * resources. - *

- * Note: Invoked on the main thread. - *

- */ - public void onFinish() { - /* do nothing - stub */ - } - - /** - * Gets a {@link PrinterInfo} object that contains metadata about the - * printed content. - *

- * Note: Invoked on the main thread. - *

- * - * @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 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.aidl b/core/java/android/print/PrintAdapterInfo.aidl deleted file mode 100644 index 27bf717f6d20..000000000000 --- a/core/java/android/print/PrintAdapterInfo.aidl +++ /dev/null @@ -1,19 +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; - -parcelable PrintAdapterInfo; 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 CREATOR = - new Creator() { - @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. + * + *

Lifecycle

+ *

+ *

    + *
  • + * Initially, you will receive a call to {@link #onStart()}. This callback + * can be used to allocate resources. + *
  • + *
  • + * 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. + *
  • + *
  • + * 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. + *
  • + *
  • + * Finally, you will receive a call to {@link #onFinish()}. You can use this + * callback to release resources allocated in {@link #onStart()}. + *
  • + *
+ *

+ */ +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. + *

+ * After you are done laying out, you must invoke: {@link LayoutResultCallback + * #onLayoutFinished(PrintDocumentInfo, boolean)} with the last argument true + * or false depending on whether the layout changed the + * content or not, respectively; and {@link LayoutResultCallback#onLayoutFailed( + * CharSequence), if an error occurred. + *

+ *

+ * Note: 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. + *

+ * + * @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. + *

+ * After you are done writing, you should not close the + * file descriptor, rather you must invoke: {@link WriteResultCallback + * #onWriteFinished()}, if writing completed successfully; or {@link + * WriteResultCallback#onWriteFailed(CharSequence)}, if an error occurred. + *

+ *

+ * Note: 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. + *

+ * + * @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 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 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/PrintDocumentInfo.aidl b/core/java/android/print/PrintDocumentInfo.aidl new file mode 100644 index 000000000000..831dcb7ba1ae --- /dev/null +++ b/core/java/android/print/PrintDocumentInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +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 CREATOR = + new Creator() { + @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/PrintFileAdapter.java b/core/java/android/print/PrintFileAdapter.java deleted file mode 100644 index dab964846552..000000000000 --- a/core/java/android/print/PrintFileAdapter.java +++ /dev/null @@ -1,125 +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.AsyncTask; -import android.os.CancellationSignal; -import android.os.CancellationSignal.OnCancelListener; -import android.util.Log; - -import libcore.io.IoUtils; - -import java.io.File; -import java.io.FileDescriptor; -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; - -/** - * Adapter for printing files. - */ -class PrintFileAdapter extends PrintAdapter { - - private static final String LOG_TAG = "PrintFileAdapter"; - - private final File mFile; - - private WriteFileAsyncTask mWriteFileAsyncTask; - - public PrintFileAdapter(File file) { - if (file == null) { - throw new IllegalArgumentException("File cannot be null!"); - } - mFile = file; - } - - @Override - public void onPrint(List pages, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback callback) { - mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal, - callback); - mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, - (Void[]) null); - - } - - @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 { - - private final File mSource; - - private final FileDescriptor mDestination; - - private final PrintResultCallback mResultCallback; - - private final CancellationSignal mCancellationSignal; - - public WriteFileAsyncTask(File source, FileDescriptor destination, - CancellationSignal cancellationSignal, PrintResultCallback callback) { - mSource = source; - mDestination = destination; - mResultCallback = callback; - mCancellationSignal = cancellationSignal; - mCancellationSignal.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel() { - cancel(true); - } - }); - } - - @Override - protected Void doInBackground(Void... params) { - InputStream in = null; - OutputStream out = new FileOutputStream(mDestination); - final byte[] buffer = new byte[8192]; - try { - in = new FileInputStream(mSource); - while (true) { - final int readByteCount = in.read(buffer); - if (readByteCount < 0) { - break; - } - out.write(buffer, 0, readByteCount); - } - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error writing data!", ioe); - } finally { - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(out); - if (!isCancelled()) { - List pages = new ArrayList(); - pages.add(PageRange.ALL_PAGES); - mResultCallback.onPrintFinished(pages); - } else { - mResultCallback.onPrintFailed("Cancelled"); - } - } - return null; - } - } -} - 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 getPrintJobs() { try { - List printJobInfos = mService.getPrintJobs(mAppId, mUserId); + List 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 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 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 pages = (List) args.arg2; - final FileDescriptor fd = (FileDescriptor) args.arg3; - IPrintResultCallback callback = (IPrintResultCallback) args.arg4; + List pages = (List) 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 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 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 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(prototype.mInputTrays) : null; + mOutputTrays = (prototype.mOutputTrays != null) + ? new ArrayList(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 { *

*/ 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(); + if (mPrototype.mInputTrays == null) { + mPrototype.mInputTrays = new ArrayList(); } - 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(); + if (mPrototype.mOutputTrays == null) { + mPrototype.mOutputTrays = new ArrayList(); } - 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 getPrintJobs(); - PrintJobInfo getPrintJob(int printJobId); + List 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 printers); - oneway void removeDiscoveredPrinters(in List 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. + *

+ * Note: It is your responsibility to close the file descriptor. + *

+ * + * @return A file descriptor for reading the data or null. + */ + 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; @@ -80,6 +73,15 @@ public final class PrintJob { return mCachedInfo; } + /** + * 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. - *

- * Note: It is your responsibility to close the file descriptor. - *

- * - * @return A file descriptor for reading the data or null. - */ - 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()}. *

*

* 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. + *

+ *

+ * 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()}. *

*

Lifecycle

*

@@ -124,9 +129,9 @@ import java.util.List; *

* 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. *

*

* 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 { * <{@link android.R.styleable#PrintService print-service}> * tag. This is a a sample XML file configuring a print service: *

 <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.
      * 

* * @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 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. *

* * @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 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 getPrintJobs() { + final IPrintServiceClient client; synchronized (mLock) { - if (mClient == null) { - throw new IllegalStateException("Print serivice not connected!"); - } - try { - List printJobs = null; - List printJobInfos = mClient.getPrintJobs(); - if (printJobInfos != null) { - final int printJobInfoCount = printJobInfos.size(); - printJobs = new ArrayList(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 printJobs = null; + List printJobInfos = client.getPrintJobInfos(); + if (printJobInfos != null) { + final int printJobInfoCount = printJobInfos.size(); + printJobs = new ArrayList(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(""); - } + 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 @@ - + ISO A0 - + ISO A1 - + ISO A2 - + ISO A3 - + ISO A4 - + ISO A5 - + ISO A6 - + ISO A7 - + ISO A8 - + ISO A9 - + ISO A10 - + ISO B0 - + ISO B1 - + ISO B2 - + ISO B3 - + ISO B4 - + ISO B5 - + ISO B6 - + ISO B7 - + ISO B8 - + ISO B9 - + ISO B10 - + ISO C0 - + ISO C1 - + ISO C2 - + ISO C3 - + ISO C4 - + ISO C5 - + ISO C6 - + ISO C7 - + ISO C8 - + ISO C9 - + ISO C10 - + Letter - + Government Letter - + Legal - + Junior Legal - + Ledger - + Tabloid 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> mTaskQueue = new ArrayList>(); - - 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> 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(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 pages = new ArrayList(); - pages.add(PageRange.ALL_PAGES); - - new QueuedAsyncTask(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 pages = new ArrayList(); + pages.add(PageRange.ALL_PAGES); + + mRemotePrintAdapter.write(pages, new WriteResultCallback() { + @Override + public void onWriteFinished(List 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() { - @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 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 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 extends AsyncTask { - - private final List> mPendingOrRunningTasks; - - public QueuedAsyncTask(List> 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 { 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 mPrintJobs = new ArrayList(); 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 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> activeJobsPerServiceMap = + new HashMap>(); + + 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 jobsPerService = activeJobsPerServiceMap.get(service); + if (jobsPerService == null) { + jobsPerService = new ArrayList(); + 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> entry + : activeJobsPerServiceMap.entrySet()) { + ComponentName service = entry.getKey(); + List 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 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 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 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 mTaskQueue = new ArrayList(); + + 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 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 { + 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 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 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 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 1c1b0020b462..bd23cbc671e4 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(); } - 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(); 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 6823f1363fc8..f300642da0ba 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 mServices = - new HashMap(); - - private final List mInstalledServices = new ArrayList(); - - private final Set mEnabledServiceNames = new HashSet(); - private final Context mContext; - private final RemoteSpooler mSpooler; - - private final int mMyUid; + private final SparseArray mUserStates = new SparseArray(); 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 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 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 services = new ArrayList(); - 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 services = new ArrayList(); - 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 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 iterator = mEnabledServiceNames.iterator(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator 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 iterator = mEnabledServiceNames.iterator(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + boolean stoppedSomePackages = false; + Iterator 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 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 tempEnabledServiceNameSet = new HashSet(); - 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 tempPrintServices = new HashSet(); - - List 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 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 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> notifications = - new HashMap>(); + private void removeUser(int removedUserId) { synchronized (mLock) { - for (PrintServiceClient service : mServices.values()) { - List 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> notification - : notifications.entrySet()) { - PrintServiceClient service = notification.getKey(); - List 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 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 printers) { - throwIfPrinterIdsForPrinterInfoTampered(printers); - synchronized (mLock) { - if (mPrinterDiscoveryObserver != null) { - try { - mPrinterDiscoveryObserver.addDiscoveredPrinters(printers); - } catch (RemoteException re) { - /* ignore */ - } - } - } - } - - @Override - public void removeDiscoveredPrinters(List 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 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 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 mPendingCommands = new ArrayList(); + + 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 mWeakService; + + public RemotePrintServiceClient(RemotePrintService service) { + mWeakService = new WeakReference(service); + } + + @Override + public List 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 printers) { + throwIfPrinterIdsForPrinterInfoTampered(printers); + try { + mDecoratedObsever.addDiscoveredPrinters(printers); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error delegating to addDiscoveredPrinters", re); + } + } + + @Override + public void removeDiscoveredPrinters(List printerIds) { + throwIfPrinterIdsTampered(printerIds); + try { + mDecoratedObsever.removeDiscoveredPrinters(printerIds); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error delegating to removeDiscoveredPrinters", re); + } + } + + private void throwIfPrinterIdsForPrinterInfoTampered( + List 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 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 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> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfosCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfosResult(List printJobs, int sequence) { + onRemoteMethodResult(printJobs, sequence); + } + }; + } + + public List 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 { + 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 { + 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 { + 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 { + 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 { + 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 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 mWeakSpooler; + + public PrintSpoolerClient(RemotePrintSpooler spooler) { + mWeakSpooler = new WeakReference(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 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> { - private final IPrintSpoolerServiceCallbacks mCallback; - - public GetPrintJobsCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new BasePrintSpoolerServiceCallbacks() { - @Override - public void onGetPrintJobsResult(List printJobs, int sequence) { - onRemoteMethodResult(printJobs, sequence); - } - }; - } - - public List 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 { - 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 { - 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 { - 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 { - 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 { - 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 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 mActiveServices = + new HashMap(); + + private final List mInstalledServices = + new ArrayList(); + + private final Set mEnabledServices = + new HashSet(); + + 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 services; + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mActiveServices.isEmpty()) { + return; + } + services = new ArrayList(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 services; + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mActiveServices.isEmpty()) { + return; + } + services = new ArrayList(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 getActiveServices() { + synchronized(mLock) { + throwIfDestroyedLocked(); + return mActiveServices; + } + } + + public Set 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 tempPrintServices = new HashSet(); + + List 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 tempEnabledServiceNameSet = new HashSet(); + + 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."); + } + } +} + -- cgit v1.2.3-59-g8ed1b