diff options
author | Svetoslav Ganov <svetoslavganov@google.com> | 2013-08-11 12:29:39 -0700 |
---|---|---|
committer | Svetoslav Ganov <svetoslavganov@google.com> | 2013-08-11 14:40:05 -0700 |
commit | 798bed6cc7d273e72b0253288605db9cd2b57740 (patch) | |
tree | b4278847c40cf910b69773c6205395ada02543ed /core/java/android/printservice | |
parent | 5893a97cbf398ca3e1bff5444454343d94e25a4c (diff) | |
download | frameworks_base-798bed6cc7d273e72b0253288605db9cd2b57740.zip frameworks_base-798bed6cc7d273e72b0253288605db9cd2b57740.tar.gz frameworks_base-798bed6cc7d273e72b0253288605db9cd2b57740.tar.bz2 |
Refinement of the print service APIs.
1. Factored out the printer discovery APIs of a print service in a
dedicated session object that is created by the print service on
demand. This ensures that added/removed/updated printers from
one session do not interfere with another session.
2. Updated the app facing APIs to pass in a document info along
with a printed file. Also exposed the print file adapter so
apps that create a temporary file for printing can intercept
when it is read by the system so the file can be deleted.
3. Updated the print service documentation.
Change-Id: I3473d586c26d8bda1cf7e2bdacb441aa9df982ed
Diffstat (limited to 'core/java/android/printservice')
-rw-r--r-- | core/java/android/printservice/IPrintService.aidl | 12 | ||||
-rw-r--r-- | core/java/android/printservice/PrintDocument.java | 10 | ||||
-rw-r--r-- | core/java/android/printservice/PrintJob.java | 57 | ||||
-rw-r--r-- | core/java/android/printservice/PrintService.java | 433 | ||||
-rw-r--r-- | core/java/android/printservice/PrinterDiscoverySession.java | 357 | ||||
-rw-r--r-- | core/java/android/printservice/package.html | 24 |
6 files changed, 567 insertions, 326 deletions
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl index e6fdbf9..16b7a26 100644 --- a/core/java/android/printservice/IPrintService.aidl +++ b/core/java/android/printservice/IPrintService.aidl @@ -10,16 +10,14 @@ * 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 + * See the License for the specific languag˙e governing permissions and * limitations under the License. */ package android.printservice; -import android.os.ICancellationSignal; -import android.print.IPrinterDiscoveryObserver; +import android.print.IPrinterDiscoverySessionObserver; import android.print.PrintJobInfo; -import android.print.PrinterId; import android.printservice.IPrintServiceClient; /** @@ -29,9 +27,7 @@ import android.printservice.IPrintServiceClient; */ oneway interface IPrintService { void setClient(IPrintServiceClient client); - void onRequestUpdatePrinters(in List<PrinterId> printerIds); - void onRequestCancelPrintJob(in PrintJobInfo printJobInfo); + void requestCancelPrintJob(in PrintJobInfo printJobInfo); void onPrintJobQueued(in PrintJobInfo printJobInfo); - void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer); - void onStopPrinterDiscovery(); + void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer); } diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java index 2a1581a..7437dc5 100644 --- a/core/java/android/printservice/PrintDocument.java +++ b/core/java/android/printservice/PrintDocument.java @@ -55,14 +55,14 @@ public final class PrintDocument { } /** - * 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. + * Gets the data associated with this document. * <p> - * <strong>Note:</strong> It is your responsibility to close the file descriptor. + * <strong>Note: </strong> It is a responsibility of the client to open a + * stream to the returned file descriptor, fully read the data, and close + * the file descriptor. * </p> * - * @return A file descriptor for reading the data or <code>null</code>. + * @return A file descriptor for reading the data. */ public FileDescriptor getData() { ParcelFileDescriptor source = null; diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java index 64c079e..5f9a730 100644 --- a/core/java/android/printservice/PrintJob.java +++ b/core/java/android/printservice/PrintJob.java @@ -21,9 +21,9 @@ import android.print.PrintJobInfo; import android.util.Log; /** - * This class represents a print job from the perspective of a - * print service. It provides APIs for observing the print job - * state and performing operations on the print job. + * This class represents a print job from the perspective of a print + * service. It provides APIs for observing the print job state and + * performing operations on the print job. */ public final class PrintJob { @@ -38,7 +38,8 @@ public final class PrintJob { PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) { mCachedInfo = jobInfo; mPrintServiceClient = client; - mDocument = new PrintDocument(mCachedInfo.getId(), client, jobInfo.getDocumentInfo()); + mDocument = new PrintDocument(mCachedInfo.getId(), client, + jobInfo.getDocumentInfo()); } /** @@ -77,7 +78,7 @@ public final class PrintJob { } /** - * Gets the document of this print job. + * Gets the printed document. * * @return The document. */ @@ -87,11 +88,12 @@ public final class PrintJob { /** * Gets whether this print job is queued. Such a print job is - * ready to be printed and can be started. + * ready to be printed and can be started or cancelled. * * @return Whether the print job is queued. * * @see #start() + * @see #cancel() */ public boolean isQueued() { return getInfo().getState() == PrintJobInfo.STATE_QUEUED; @@ -112,6 +114,42 @@ public final class PrintJob { } /** + * Gets whether this print job is completed. Such a print job + * is successfully printed. This is a final state. + * + * @return Whether the print job is completed. + * + * @see #complete() + */ + public boolean isCompleted() { + return getInfo().getState() == PrintJobInfo.STATE_COMPLETED; + } + + /** + * Gets whether this print job is failed. Such a print job is + * not successfully printed due to an error. This is a final state. + * + * @return Whether the print job is failed. + * + * @see #fail(CharSequence) + */ + public boolean isFailed() { + return getInfo().getState() == PrintJobInfo.STATE_FAILED; + } + + /** + * Gets whether this print job is cancelled. Such a print job was + * cancelled as a result of a user request. This is a final state. + * + * @return Whether the print job is cancelled. + * + * @see #cancel() + */ + public boolean isCancelled() { + return getInfo().getState() == PrintJobInfo.STATE_FAILED; + } + + /** * Starts the print job. You should call this method if {@link * #isQueued()} returns true and you started printing. * @@ -163,12 +201,13 @@ public final class PrintJob { /** * Cancels the print job. You should call this method if {@link * #isQueued()} or {@link #isStarted()} returns true and you canceled - * the print job as a response to a call to {@link PrintService - * #onRequestCancelPrintJob(PrintJob)}. + * the print job as a response to a call to {@link + * PrintService#onRequestCancelPrintJob(PrintJob)}. * - * @return Whether the job as canceled. + * @return Whether the job is canceled. * * @see #isStarted() + * @see #isQueued() */ public boolean cancel() { if (isQueued() || isStarted()) { diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index 49384db..92bccd4 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -25,10 +25,9 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.print.IPrinterDiscoveryObserver; +import android.print.IPrinterDiscoverySessionObserver; import android.print.PrintJobInfo; import android.print.PrinterId; -import android.print.PrinterInfo; import android.util.Log; import java.util.ArrayList; @@ -37,84 +36,82 @@ import java.util.List; /** * <p> - * This is the base class for implementing print services. A print service - * knows how to discover and interact one or more printers via one or more - * protocols. + * This is the base class for implementing print services. A print service knows + * how to discover and interact one or more printers via one or more protocols. * </p> * <h3>Printer discovery</h3> * <p> - * A print service is responsible for discovering and reporting printers. - * A printer discovery period starts with a call to - * {@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 reports added printers - * that disappeared by calling {@link #removeDiscoveredPrinters(List)}. - * Calls to {@link #addDiscoveredPrinters(List)} and - * {@link #removeDiscoveredPrinters(List)} before a call to - * {@link #onStartPrinterDiscovery()} and after a call to - * {@link #onStopPrinterDiscovery()} are a no-op. + * A print service is responsible for discovering printers, adding discovered printers, + * removing added printers, and updating added printers. When the system is interested + * in printers managed by your service it will call {@link + * #onCreatePrinterDiscoverySession()} from which you must return a new {@link + * PrinterDiscoverySession} instance. The returned session encapsulates the interaction + * between the system and your service during printer discovery. For description of this + * interaction refer to the documentation for {@link PrinterDiscoverySession}. * </p> * <p> - * For every printer discovery period all printers have to be added. Each - * printer known to this print service should be added only once during a - * discovery period, unless it was added and then removed before that. - * Only an already added printer can be removed. + * For every printer discovery session all printers have to be added since system does + * not retain printers across sessions. Hence, each printer known to this print service + * should be added only once during a discovery session. Only an already added printer + * can be removed or updated. Removed printers can be added again. * </p> * <h3>Print jobs</h3> * <p> - * When a new print job targeted to the printers managed by this print - * service is queued, i.e. ready for processing by the print service, - * 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()}. + * When a new print job targeted to a printer managed by this print service is is queued, + * i.e. ready for processing by the print service, you will receive a call to {@link + * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately + * or schedule that for an appropriate time in the future. The list of all active print + * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active + * print jobs are ones that are queued or started. * </p> * <p> - * A print service is responsible for setting the print job state as - * appropriate while processing it. Initially, a print job is in a - * {@link PrintJobInfo#STATE_QUEUED} state which means that the data to - * be printed is spooled by the system and the print service can obtain - * that data by calling {@link PrintJob#getDocument()}. A queued print - * job's {@link PrintJob#isQueued()} method returns true. + * A print service is responsible for setting a print job's state as appropriate + * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued() + * PrintJob.isQueued()} returns true, which means that the document to be printed is + * spooled by the system and the print service can begin processing it. You can obtain + * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()} + * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}. + * After the print service starts printing the data it should set the print job's + * state to started by calling {@link PrintJob#start()} after which + * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful + * completion, the print job should be marked as completed by calling {@link + * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted() + * PrintJob.isCompleted()} would return true. In case of a failure, the print job should + * be marked as failed by calling {@link PrintJob#fail(CharSequence) PrintJob.fail( + * CharSequence)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would + * return true. * </p> * <p> - * After the print service starts printing the data it should set the - * print job state to {@link PrintJobInfo#STATE_STARTED} by calling - * {@link PrintJob#start()}. Upon successful completion, the print job - * state has to be set to {@link PrintJobInfo#STATE_COMPLETED} by calling - * {@link PrintJob#complete()}. In case of a failure, the print job - * state should be set to {@link PrintJobInfo#STATE_FAILED} by calling - * {@link PrintJob#fail(CharSequence)}. If a print job is in a - * {@link PrintJobInfo#STATE_STARTED} state, i.e. {@link PrintJob#isStarted()} - * return true, and the user requests to cancel it, the print service will - * receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which - * requests from the service to do a best effort in canceling the job. In - * case the job is successfully canceled, its state has to be set to - * {@link PrintJobInfo#STATE_CANCELED}. by calling {@link PrintJob#cancel()}. + * If a print job is queued or started 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 best effort in canceling the job. In case the job + * is successfully canceled, its state has to be marked as cancelled by calling {@link + * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled() + * PrintJob.isCacnelled()} would return true. * </p> * <h3>Lifecycle</h3> * <p> - * The lifecycle of a print service is managed exclusively by the system - * and follows the established service lifecycle. Additionally, starting - * or stopping a print service is triggered exclusively by an explicit - * user action through enabling or disabling it in the device settings. - * After the system binds to a print service, it calls {@link #onConnected()}. - * This method can be overriden by clients to perform post binding setup. - * Also after the system unbinds from a print service, it calls - * {@link #onDisconnected()}. This method can be overriden by clients to - * perform post unbinding cleanup. + * The lifecycle of a print service is managed exclusively by the system and follows + * the established service lifecycle. Additionally, starting or stopping a print service + * is triggered exclusively by an explicit user action through enabling or disabling it + * in the device settings. After the system binds to a print service, it calls {@link + * #onConnected()}. This method can be overriden by clients to perform post binding setup. + * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}. + * This method can be overriden by clients to perform post unbinding cleanup. Your should + * not do any work after the system disconnected from your print service since the + * service can be killed at any time to reclaim memory. The system will not disconnect + * from a print service if there are active print jobs for the printers managed by it. * </p> * <h3>Declaration</h3> * <p> - * A print service is declared as any other service in an AndroidManifest.xml - * but it must also specify that it handles the {@link android.content.Intent} - * with action {@link #SERVICE_INTERFACE}. Failure to declare this intent - * will cause the system to ignore the print service. Additionally, a print - * service must request the {@link android.Manifest.permission#BIND_PRINT_SERVICE} - * permission to ensure that only the system can bind to it. Failure to - * declare this intent will cause the system to ignore the print service. - * Following is an example declaration: + * A print service is declared as any other service in an AndroidManifest.xml but it must + * also specify that it handles the {@link android.content.Intent} with action {@link + * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent + * will cause the system to ignore the print service. Additionally, a print service must + * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE + * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can + * bind to it. Failure to declare this intent will cause the system to ignore the print + * service. Following is an example declaration: * </p> * <pre> * <service android:name=".MyPrintService" @@ -127,17 +124,15 @@ import java.util.List; * </pre> * <h3>Configuration</h3> * <p> - * A print service can be configured by specifying an optional settings - * activity which exposes service specific options, an optional add - * prints activity which is used for manual addition of printers, vendor - * name ,etc. It is a responsibility of the system to launch the settings - * and add printers activities when appropriate. + * A print service can be configured by specifying an optional settings activity which + * exposes service specific settings, an optional add printers activity which is used for + * manual addition of printers, vendor name ,etc. It is a responsibility of the system + * to launch the settings and add printers activities when appropriate. * </p> * <p> - * A print service is configured by providing a - * {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring - * the service. A service declaration with a meta-data tag is presented - * below: + * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data} + * entry in the manifest when declaring the service. A service declaration with a meta-data + * tag is presented below: * <pre> <service android:name=".MyPrintService" * android:permission="android.permission.BIND_PRINT_SERVICE"> * <intent-filter> @@ -147,8 +142,9 @@ import java.util.List; * </service></pre> * </p> * <p> - * For more details refer to {@link #SERVICE_META_DATA} and - * <code><{@link android.R.styleable#PrintService print-service}></code>. + * For more details for how to configure your print service via the meta-data refer to + * {@link #SERVICE_META_DATA} and <code><{@link android.R.styleable#PrintService + * print-service}></code>. * </p> */ public abstract class PrintService extends Service { @@ -157,21 +153,25 @@ public abstract class PrintService extends Service { /** * The {@link Intent} action that must be declared as handled by a service - * in its manifest to allow the system to recognize it as a print service. + * in its manifest for the system to recognize it as a print service. */ public static final String SERVICE_INTERFACE = "android.printservice.PrintService"; /** - * Name under which a PrintService component publishes additional information - * about itself. This meta-data must reference an XML resource containing a - * <code><{@link android.R.styleable#PrintService print-service}></code> - * tag. This is a a sample XML file configuring a print service: + * Name under which a {@link PrintService} component publishes additional information + * about itself. This meta-data must reference a XML resource containing a <code> + * <{@link android.R.styleable#PrintService print-service}></code> tag. This is + * a sample XML file configuring a print service: * <pre> <print-service * android:vendor="SomeVendor" * android:settingsActivity="foo.bar.MySettingsActivity" * andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity." * . . . * /></pre> + * <p> + * For detailed configuration options that can be specified via the meta-data + * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}. + * </p> */ public static final String SERVICE_META_DATA = "android.printservice"; @@ -181,12 +181,12 @@ public abstract class PrintService extends Service { private IPrintServiceClient mClient; - private IPrinterDiscoveryObserver mDiscoveryObserver; + private int mLastSessionId = -1; @Override - protected void attachBaseContext(Context base) { + protected final void attachBaseContext(Context base) { super.attachBaseContext(base); - mHandler = new MyHandler(base.getMainLooper()); + mHandler = new ServiceHandler(base.getMainLooper()); } /** @@ -204,203 +204,47 @@ public abstract class PrintService extends Service { } /** - * Callback requesting from this service to start printer discovery. - * At the end of the printer discovery period the system will call - * {@link #onStopPrinterDiscovery()}. Discovered printers should be - * reported by calling {@link #addDiscoveredPrinters(List)} and reported - * ones that disappear should be reported by calling - * {@link #removeDiscoveredPrinters(List)}. - * - * @see #onStopPrinterDiscovery() - * @see #addDiscoveredPrinters(List) - * @see #removeDiscoveredPrinters(List) - * @see #updateDiscoveredPrinters(List) - */ - protected abstract void onStartPrinterDiscovery(); - - /** - * Callback requesting from this service to stop printer discovery. - * - * @see #onStartPrinterDiscovery() - * @see #addDiscoveredPrinters(List) - * @see #removeDiscoveredPrinters(List) - * @see #updateDiscoveredPrinters(List) - */ - protected abstract void onStopPrinterDiscovery(); - - /** - * Adds discovered printers. This method should be called during a - * printer discovery period, i.e. after a call to - * {@link #onStartPrinterDiscovery()} and before the corresponding - * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing. - * <p> - * <strong>Note:</strong> For every printer discovery period all - * 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. - * </p> - * <p> - * A {@link PrinterInfo} can have all of its required attributes specified, - * or not. Whether all attributes are specified can be verified by calling - * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers - * regardless if all required attributes are specified. When the system - * (and the user) needs to interact with a printer, you will receive a - * call to {@link #onRequestUpdatePrinters(List)}. If you fail to update - * a printer that was added without all required attributes via calling - * {@link #updateDiscoveredPrinters(List)}, then this printer will be - * ignored, i.e. considered unavailable. - * <p> - * - * @param printers A list with discovered printers. - * - * @see #updateDiscoveredPrinters(List) - * @see #removeDiscoveredPrinters(List) - * @see #onStartPrinterDiscovery() - * @see #onStopPrinterDiscovery() - */ - public final void addDiscoveredPrinters(List<PrinterInfo> printers) { - final IPrinterDiscoveryObserver observer; - synchronized (mLock) { - observer = mDiscoveryObserver; - } - if (observer != null) { - try { - observer.onPrintersAdded(printers); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error adding discovered printers", re); - } - } - } - - /** - * Removes discovered printers given their ids. This method should be called - * during a printer discovery period, i.e. after a call to - * {@link #onStartPrinterDiscovery()} and before the corresponding - * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing. - * <p> - * For every printer discovery period all printers have to be added. You - * should remove only printers that were added in this printer discovery - * period by a call to {@link #addDiscoveredPrinters(List)}. You can call - * 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, it will be ignored. - * </p> + * Callback asking you to create a new {@link PrinterDiscoverySession}. * - * @param printerIds A list with disappeared printer ids. - * - * @see #addDiscoveredPrinters(List) - * @see #updateDiscoveredPrinters(List) - * @see #onStartPrinterDiscovery() - * @see #onStopPrinterDiscovery() + * @see PrinterDiscoverySession */ - public final void removeDiscoveredPrinters(List<PrinterId> printerIds) { - final IPrinterDiscoveryObserver observer; - synchronized (mLock) { - observer = mDiscoveryObserver; - } - if (observer != null) { - try { - observer.onPrintersRemoved(printerIds); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error removing discovered printers", re); - } - } - } + protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession(); /** - * Updates discovered printers that are already added. This method should - * be called during a printer discovery period, i.e. after a call to - * {@link #onStartPrinterDiscovery()} and before the corresponding - * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing. - * <p> - * For every printer discovery period all printers have to be added. You - * should update only printers that were added in this printer discovery - * period by a call to {@link #addDiscoveredPrinters(List)}. You can call - * this method as many times as necessary during the discovery period - * but should not try to update already removed or never added printers. - * If a printer is already removed or never added, it will be ignored. - * </p> - * - * @param printers A list with updated printers. - * - * @see #addDiscoveredPrinters(List) - * @see #removeDiscoveredPrinters(List) - * @see #onStartPrinterDiscovery() - * @see #onStopPrinterDiscovery() - */ - public final void updateDiscoveredPrinters(List<PrinterInfo> printers) { - final IPrinterDiscoveryObserver observer; - synchronized (mLock) { - observer = mDiscoveryObserver; - } - if (observer != null) { - try { - observer.onPrintersUpdated(printers); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error updating discovered printers", re); - } - } - } - - /** - * Called when the system will start interacting with a printer - * giving you a change to update it in case some of its capabilities - * have changed. For example, this method will be called when the - * user selects a printer. Hence, it updating this printer should - * be done as quickly as possible in order to achieve maximally - * smooth user experience. - * <p> - * A {@link PrinterInfo} can have all of its required attributes specified, - * or not. Whether all attributes are specified can be verified by calling - * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers - * regardless if all required attributes are specified. When the system - * (and the user) needs to interact with a printer, you will receive a - * call to this method. If you fail to update a printer that was added - * without all required attributes via calling - * {@link #updateDiscoveredPrinters(List)}, then this printer will be - * ignored, i.e. considered unavailable. - * </p> - * - * @param printerIds The printers to be updated. - */ - protected void onRequestUpdatePrinters(List<PrinterId> printerIds) { - } - - /** - * Called when canceling of a print job is requested. The service + * Called when cancellation of a print job is requested. The service * should do best effort to fulfill the request. After the cancellation - * is performed, the print job should be set to a cancelled state by + * is performed, the print job should be marked as cancelled state by * calling {@link PrintJob#cancel()}. * - * @param printJob The print job to be canceled. + * @param printJob The print job to cancel. + * + * @see PrintJob#cancel() PrintJob.cancel() + * @see PrintJob#isCancelled() PrintJob.isCancelled() */ - protected void onRequestCancelPrintJob(PrintJob printJob) { - } + protected abstract void onRequestCancelPrintJob(PrintJob printJob); /** * 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#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} by - * calling {@link PrintJob#isQueued()}. + * managed by this print service. * * @param printJob The new queued print job. * - * @see #getPrintJobs() + * @see PrintJob#isQueued() PrintJob.isQueued() + * @see #getActivePrintJobs() */ protected abstract void onPrintJobQueued(PrintJob printJob); /** - * Gets the print jobs for the printers managed by this service. + * Gets the active print jobs for the printers managed by this service. + * Active print jobs are ones that are not in a final state, i.e. whose + * state is queued or started. * - * @return The print jobs. + * @return The active print jobs. + * + * @see PrintJob#isQueued() PrintJob.isQueued() + * @see PrintJob#isStarted() PrintJob.isStarted() */ - public final List<PrintJob> getPrintJobs() { + public final List<PrintJob> getActivePrintJobs() { final IPrintServiceClient client; synchronized (mLock) { client = mClient; @@ -428,14 +272,14 @@ public abstract class PrintService extends Service { } /** - * Generates a global printer id given the printer's locally unique name. + * Generates a global printer id given the printer's locally unique one. * - * @param printerName The printer name. + * @param localId A locally unique id in the context of your print service. * @return Global printer id. */ - public final PrinterId generatePrinterId(String printerName) { + public final PrinterId generatePrinterId(String localId) { return new PrinterId(new ComponentName(getPackageName(), - getClass().getName()), printerName); + getClass().getName()), localId); } @Override @@ -443,69 +287,58 @@ public abstract class PrintService extends Service { return new IPrintService.Stub() { @Override public void setClient(IPrintServiceClient client) { - mHandler.obtainMessage(MyHandler.MSG_SET_CLEINT, client).sendToTarget(); + mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client) + .sendToTarget(); } @Override - public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) { - mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY, + public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION, observer).sendToTarget(); } @Override - public void onStopPrinterDiscovery() { - mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY); - } - - @Override - public void onRequestUpdatePrinters(List<PrinterId> printerIds) { - mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS, - printerIds).sendToTarget(); - } - - @Override - public void onRequestCancelPrintJob(PrintJobInfo printJobInfo) { - mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB, + public void requestCancelPrintJob(PrintJobInfo printJobInfo) { + mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB, printJobInfo).sendToTarget(); } @Override public void onPrintJobQueued(PrintJobInfo printJobInfo) { - mHandler.obtainMessage(MyHandler.MSG_ON_PRINTJOB_QUEUED, + mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED, printJobInfo).sendToTarget(); } }; } - private final class MyHandler extends Handler { - public static final int MSG_ON_START_PRINTER_DISCOVERY = 1; - public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2; + private final class ServiceHandler extends Handler { + public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1; + public static final int MSG_ON_PRINTJOB_QUEUED = 2; public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3; - public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 4; - public static final int MSG_ON_PRINTJOB_QUEUED = 5; - public static final int MSG_SET_CLEINT = 6; + public static final int MSG_SET_CLEINT = 4; - public MyHandler(Looper looper) { + public ServiceHandler(Looper looper) { super(looper, null, true); } @Override - @SuppressWarnings("unchecked") public void handleMessage(Message message) { final int action = message.what; switch (action) { - case MSG_ON_START_PRINTER_DISCOVERY: { - synchronized (mLock) { - mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj; + case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: { + IPrinterDiscoverySessionObserver observer = + (IPrinterDiscoverySessionObserver) message.obj; + PrinterDiscoverySession session = onCreatePrinterDiscoverySession(); + if (session == null) { + throw new NullPointerException("session cannot be null"); } - onStartPrinterDiscovery(); - } break; - - case MSG_ON_STOP_PRINTER_DISCOVERY: { synchronized (mLock) { - mDiscoveryObserver = null; + if (session.getId() == mLastSessionId) { + throw new IllegalStateException("cannot reuse sessions"); + } + mLastSessionId = session.getId(); } - onStopPrinterDiscovery(); + session.setObserver(observer); } break; case MSG_ON_REQUEST_CANCEL_PRINTJOB: { @@ -513,11 +346,6 @@ public abstract class PrintService extends Service { onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient)); } break; - case MSG_ON_REQUEST_UPDATE_PRINTERS: { - List<PrinterId> printerIds = (List<PrinterId>) message.obj; - onRequestUpdatePrinters(printerIds); - } break; - case MSG_ON_PRINTJOB_QUEUED: { PrintJobInfo printJobInfo = (PrintJobInfo) message.obj; onPrintJobQueued(new PrintJob(printJobInfo, mClient)); @@ -527,9 +355,6 @@ public abstract class PrintService extends Service { IPrintServiceClient client = (IPrintServiceClient) message.obj; synchronized (mLock) { mClient = client; - if (client == null) { - mDiscoveryObserver = null; - } } if (client != null) { onConnected(); diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java new file mode 100644 index 0000000..5bc0f2e --- /dev/null +++ b/core/java/android/printservice/PrinterDiscoverySession.java @@ -0,0 +1,357 @@ +/* + * 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.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.print.IPrinterDiscoverySessionController; +import android.print.IPrinterDiscoverySessionObserver; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * This class encapsulates the interaction between a print service and the + * system during printer discovery. During printer discovery you are responsible + * for adding discovered printers, removing already added printers that + * disappeared, and updating already added printers. + * <p> + * The opening of the session is announced by a call to {@link + * PrinterDiscoverySession#onOpen(List)} at which point you should start printer + * discovery. The closing of the session is announced by a call to {@link + * PrinterDiscoverySession#onClose()} at which point you should stop printer + * discovery. Discovered printers are added by invoking {@link + * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared + * are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. + * Added printers whose properties or capabilities changed are updated through + * a call to {@link PrinterDiscoverySession#updatePrinters(List)}. + * </p> + * <p> + * The system will make a call to + * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you + * need to update a given printer. It is possible that you add a printer without + * specifying its capabilities. This enables you to avoid querying all + * discovered printers for their capabilities, rather querying the capabilities + * of a printer only if necessary. For example, the system will require that you + * update a printer if it gets selected by the user. If you did not report the + * printer capabilities when adding it, you must do so after the system requests + * a printer update. Otherwise, the printer will be ignored. + * </p> + * <p> + * During printer discovery all printers that are known to your print service + * have to be added. The system does not retain any printers from previous + * sessions. + * </p> + */ +public abstract class PrinterDiscoverySession { + private static final String LOG_TAG = "PrinterDiscoverySession"; + + private static int sIdCounter = 0; + + private final Object mLock = new Object(); + + private final Handler mHandler; + + private final int mId; + + private IPrinterDiscoverySessionController mController; + + private IPrinterDiscoverySessionObserver mObserver; + + /** + * Constructor. + * + * @param context A context instance. + */ + public PrinterDiscoverySession(Context context) { + mId = sIdCounter++; + mHandler = new SessionHandler(context.getMainLooper()); + mController = new PrinterDiscoverySessionController(this); + } + + void setObserver(IPrinterDiscoverySessionObserver observer) { + synchronized (mLock) { + mObserver = observer; + try { + mObserver.setController(mController); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting session controller", re); + } + } + } + + int getId() { + return mId; + } + + /** + * Adds discovered printers. Adding an already added printer has no effect. + * Removed printers can be added again. You can call this method multiple + * times during printer discovery. + * <p> + * <strong>Note: </strong> Calling this method when the session is closed, + * which is if {@link #isClosed()} returns true, will throw an {@link + * IllegalStateException}. + * </p> + * + * @param printers The printers to add. + * + * @see #removePrinters(List) + * @see #updatePrinters(List) + * @see #isClosed() + */ + public final void addPrinters(List<PrinterInfo> printers) { + final IPrinterDiscoverySessionObserver observer; + synchronized (mLock) { + throwIfClosedLocked(); + observer = mObserver; + } + if (observer != null) { + try { + observer.onPrintersAdded(printers); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error adding printers", re); + } + } + } + + /** + * Removes added printers. Removing an already removed or never added + * printer has no effect. Removed printers can be added again. You + * can call this method multiple times during printer discovery. + * <p> + * <strong>Note: </strong> Calling this method when the session is closed, + * which is if {@link #isClosed()} returns true, will throw an {@link + * IllegalStateException}. + * </p> + * + * @param printerIds The ids of the removed printers. + * + * @see #addPrinters(List) + * @see #updatePrinters(List) + * @see #isClosed() + */ + public final void removePrinters(List<PrinterId> printerIds) { + final IPrinterDiscoverySessionObserver observer; + synchronized (mLock) { + throwIfClosedLocked(); + observer = mObserver; + } + if (observer != null) { + try { + observer.onPrintersRemoved(printerIds); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error removing printers", re); + } + } + } + + /** + * Updates added printers. Updating a printer that was not added or that + * was removed has no effect. You can call this method multiple times + * during printer discovery. + * <p> + * <strong>Note: </strong> Calling this method when the session is closed, + * which is if {@link #isClosed()} returns true, will throw an {@link + * IllegalStateException}. + * </p> + * + * @param printers The printers to update. + * + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #isClosed() + */ + public final void updatePrinters(List<PrinterInfo> printers) { + final IPrinterDiscoverySessionObserver observer; + synchronized (mLock) { + throwIfClosedLocked(); + observer = mObserver; + } + if (observer != null) { + try { + observer.onPrintersUpdated(printers); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error updating printers", re); + } + } + } + + /** + * Callback notifying you that the session is open and you should start + * printer discovery. Discovered printers should be added via calling + * {@link #addPrinters(List)}. Added printers that disappeared should be + * removed via calling {@link #removePrinters(List)}. Added printers whose + * properties or capabilities changes should be updated via calling {@link + * #updatePrinters(List)}. When the session is closed you will receive a + * call to {@link #onClose()}. + * <p> + * During printer discovery all printers that are known to your print + * service have to be added. The system does not retain any printers from + * previous sessions. + * </p> + * <p> + * <strong>Note: </strong>You are also given a list of printers whose + * availability has to be checked first. For example, these printers could + * be the user's favorite ones, therefore they have to be verified first. + * </p> + * + * @see #onClose() + * @see #isClosed() + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #updatePrinters(List) + */ + public abstract void onOpen(List<PrinterId> priorityList); + + /** + * Callback notifying you that the session is closed and you should stop + * printer discovery. After the session is closed and any attempt to call + * any of its methods will throw an exception. Whether a session is closed + * can be checked by calling {@link #isClosed()}. Once the session is closed + * it will never be opened again. + * + * @see #onOpen(List) + * @see #isClosed() + * @see #addPrinters(List) + * @see #removePrinters(List) + * @see #updatePrinters(List) + */ + public abstract void onClose(); + + /** + * Requests that you update a printer. You are responsible for updating + * the printer by also reporting its capabilities via calling {@link + * #updatePrinters(List)}. + * <p> + * <strong>Note: </strong> A printer can be initially added without its + * capabilities to avoid polling printers that the user will not select. + * However, after this method is called you are expected to update the + * printer <strong>including</strong> its capabilities. Otherwise, the + * printer will be ignored. + * <p> + * A scenario when you may be requested to update a printer is if the user + * selects it and the system has to present print options UI based on the + * printer's capabilities. + * </p> + * + * @param printerId The printer id. + * + * @see #updatePrinters(List) + * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo) + * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo) + */ + public abstract void onRequestPrinterUpdate(PrinterId printerId); + + /** + * Gets whether this session is closed. + * + * @return Whether the session is closed. + */ + public final boolean isClosed() { + synchronized (mLock) { + return (mController == null && mObserver == null); + } + } + + void close() { + synchronized (mLock) { + throwIfClosedLocked(); + mController = null; + mObserver = null; + } + } + + private void throwIfClosedLocked() { + if (isClosed()) { + throw new IllegalStateException("Session is closed"); + } + } + + private final class SessionHandler extends Handler { + public static final int MSG_OPEN = 1; + public static final int MSG_CLOSE = 2; + public static final int MSG_REQUEST_PRINTER_UPDATE = 3; + + public SessionHandler(Looper looper) { + super(looper, null, true); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_OPEN: { + List<PrinterId> priorityList = (List<PrinterId>) message.obj; + onOpen(priorityList); + } break; + + case MSG_CLOSE: { + onClose(); + close(); + } break; + + case MSG_REQUEST_PRINTER_UPDATE: { + PrinterId printerId = (PrinterId) message.obj; + onRequestPrinterUpdate(printerId); + } break; + } + } + } + + private static final class PrinterDiscoverySessionController extends + IPrinterDiscoverySessionController.Stub { + private final WeakReference<PrinterDiscoverySession> mWeakSession; + + public PrinterDiscoverySessionController(PrinterDiscoverySession session) { + mWeakSession = new WeakReference<PrinterDiscoverySession>(session); + } + + @Override + public void open(List<PrinterId> priorityList) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(SessionHandler.MSG_OPEN, + priorityList).sendToTarget(); + } + } + + @Override + public void close() { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE); + } + } + + @Override + public void requestPrinterUpdate(PrinterId printerId) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage( + SessionHandler.MSG_REQUEST_PRINTER_UPDATE, + printerId).sendToTarget(); + } + } + }; +} diff --git a/core/java/android/printservice/package.html b/core/java/android/printservice/package.html new file mode 100644 index 0000000..6b0327c --- /dev/null +++ b/core/java/android/printservice/package.html @@ -0,0 +1,24 @@ +<HTML> +<BODY> +<p> +Provides classes for implementing print services. Print services are plug-in components +that know how to talk to printers via some standard protocols. These services serve as a +bridge between the system and the printers. Hence, the printer and print protocol specific +implementation is factored out of the system and can by independently developed and updated. +</p> +<p> +A print service implementation should extend {@link android.printservice.PrintService} +and implement its abstract methods. Also the print service has to follow the contract for +managing print {@link android.printservice.PrintJob}s to ensure correct interaction with +the system and consistent user experience. +<p/> +<p> +The system is responsible for starting and stopping a print service depending on whether +there are active print jobs for the printers managed by the service. The print service +should also perform printer discovery in a timely fashion to ensure good user experience. +The interaction between the system and the print service during printer discovery is +encapsulated by a {@link android.printservice.PrinterDiscoverySession} instance created +by the print service when requested by the system. +</p> +</BODY> +</HTML> |