Home | History | Annotate | Download | only in printservice
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.printservice;
     18 
     19 import android.R;
     20 import android.app.Service;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.os.Handler;
     25 import android.os.IBinder;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.RemoteException;
     29 import android.print.PrintJobInfo;
     30 import android.print.PrinterId;
     31 import android.util.Log;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collections;
     35 import java.util.List;
     36 
     37 /**
     38  * <p>
     39  * This is the base class for implementing print services. A print service knows
     40  * how to discover and interact one or more printers via one or more protocols.
     41  * </p>
     42  * <h3>Printer discovery</h3>
     43  * <p>
     44  * A print service is responsible for discovering printers, adding discovered printers,
     45  * removing added printers, and updating added printers. When the system is interested
     46  * in printers managed by your service it will call {@link
     47  * #onCreatePrinterDiscoverySession()} from which you must return a new {@link
     48  * PrinterDiscoverySession} instance. The returned session encapsulates the interaction
     49  * between the system and your service during printer discovery. For description of this
     50  * interaction refer to the documentation for {@link PrinterDiscoverySession}.
     51  * </p>
     52  * <p>
     53  * For every printer discovery session all printers have to be added since system does
     54  * not retain printers across sessions. Hence, each printer known to this print service
     55  * should be added only once during a discovery session. Only an already added printer
     56  * can be removed or updated. Removed printers can be added again.
     57  * </p>
     58  * <h3>Print jobs</h3>
     59  * <p>
     60  * When a new print job targeted to a printer managed by this print service is is queued,
     61  * i.e. ready for processing by the print service, you will receive a call to {@link
     62  * #onPrintJobQueued(PrintJob)}. The print service may handle the print job immediately
     63  * or schedule that for an appropriate time in the future. The list of all active print
     64  * jobs for this service is obtained by calling {@link #getActivePrintJobs()}. Active
     65  * print jobs are ones that are queued or started.
     66  * </p>
     67  * <p>
     68  * A print service is responsible for setting a print job's state as appropriate
     69  * while processing it. Initially, a print job is queued, i.e. {@link PrintJob#isQueued()
     70  * PrintJob.isQueued()} returns true, which means that the document to be printed is
     71  * spooled by the system and the print service can begin processing it. You can obtain
     72  * the printed document by calling {@link PrintJob#getDocument() PrintJob.getDocument()}
     73  * whose data is accessed via {@link PrintDocument#getData() PrintDocument.getData()}.
     74  * After the print service starts printing the data it should set the print job's
     75  * state to started by calling {@link PrintJob#start()} after which
     76  * {@link PrintJob#isStarted() PrintJob.isStarted()} would return true. Upon successful
     77  * completion, the print job should be marked as completed by calling {@link
     78  * PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted()
     79  * PrintJob.isCompleted()} would return true. In case of a failure, the print job should
     80  * be marked as failed by calling {@link PrintJob#fail(String) PrintJob.fail(
     81  * String)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
     82  * return true.
     83  * </p>
     84  * <p>
     85  * If a print job is queued or started and the user requests to cancel it, the print
     86  * service will receive a call to {@link #onRequestCancelPrintJob(PrintJob)} which
     87  * requests from the service to do best effort in canceling the job. In case the job
     88  * is successfully canceled, its state has to be marked as cancelled by calling {@link
     89  * PrintJob#cancel() PrintJob.cancel()} after which {@link PrintJob#isCancelled()
     90  * PrintJob.isCacnelled()} would return true.
     91  * </p>
     92  * <h3>Lifecycle</h3>
     93  * <p>
     94  * The lifecycle of a print service is managed exclusively by the system and follows
     95  * the established service lifecycle. Additionally, starting or stopping a print service
     96  * is triggered exclusively by an explicit user action through enabling or disabling it
     97  * in the device settings. After the system binds to a print service, it calls {@link
     98  * #onConnected()}. This method can be overriden by clients to perform post binding setup.
     99  * Also after the system unbinds from a print service, it calls {@link #onDisconnected()}.
    100  * This method can be overriden by clients to perform post unbinding cleanup. Your should
    101  * not do any work after the system disconnected from your print service since the
    102  * service can be killed at any time to reclaim memory. The system will not disconnect
    103  * from a print service if there are active print jobs for the printers managed by it.
    104  * </p>
    105  * <h3>Declaration</h3>
    106  * <p>
    107  * A print service is declared as any other service in an AndroidManifest.xml but it must
    108  * also specify that it handles the {@link android.content.Intent} with action {@link
    109  * #SERVICE_INTERFACE android.printservice.PrintService}. Failure to declare this intent
    110  * will cause the system to ignore the print service. Additionally, a print service must
    111  * request the {@link android.Manifest.permission#BIND_PRINT_SERVICE
    112  * android.permission.BIND_PRINT_SERVICE} permission to ensure that only the system can
    113  * bind to it. Failure to declare this intent will cause the system to ignore the print
    114  * service. Following is an example declaration:
    115  * </p>
    116  * <pre>
    117  * &lt;service android:name=".MyPrintService"
    118  *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
    119  *     &lt;intent-filter&gt;
    120  *         &lt;action android:name="android.printservice.PrintService" /&gt;
    121  *     &lt;/intent-filter&gt;
    122  *     . . .
    123  * &lt;/service&gt;
    124  * </pre>
    125  * <h3>Configuration</h3>
    126  * <p>
    127  * A print service can be configured by specifying an optional settings activity which
    128  * exposes service specific settings, an optional add printers activity which is used for
    129  * manual addition of printers, vendor name ,etc. It is a responsibility of the system
    130  * to launch the settings and add printers activities when appropriate.
    131  * </p>
    132  * <p>
    133  * A print service is configured by providing a {@link #SERVICE_META_DATA meta-data}
    134  * entry in the manifest when declaring the service. A service declaration with a meta-data
    135  * tag is presented below:
    136  * <pre> &lt;service android:name=".MyPrintService"
    137  *         android:permission="android.permission.BIND_PRINT_SERVICE"&gt;
    138  *     &lt;intent-filter&gt;
    139  *         &lt;action android:name="android.printservice.PrintService" /&gt;
    140  *     &lt;/intent-filter&gt;
    141  *     &lt;meta-data android:name="android.printservice" android:resource="@xml/printservice" /&gt;
    142  * &lt;/service&gt;</pre>
    143  * </p>
    144  * <p>
    145  * For more details for how to configure your print service via the meta-data refer to
    146  * {@link #SERVICE_META_DATA} and <code>&lt;{@link android.R.styleable#PrintService
    147  * print-service}&gt;</code>.
    148  * </p>
    149  * <p>
    150  * <strong>Note: </strong> All callbacks in this class are executed on the main
    151  * application thread. You should also invoke any method of this class on the main
    152  * application thread.
    153  * </p>
    154  */
    155 public abstract class PrintService extends Service {
    156 
    157     private static final String LOG_TAG = "PrintService";
    158 
    159     private static final boolean DEBUG = false;
    160 
    161     /**
    162      * The {@link Intent} action that must be declared as handled by a service
    163      * in its manifest for the system to recognize it as a print service.
    164      */
    165     public static final String SERVICE_INTERFACE = "android.printservice.PrintService";
    166 
    167     /**
    168      * Name under which a {@link PrintService} component publishes additional information
    169      * about itself. This meta-data must reference a XML resource containing a <code>
    170      * &lt;{@link android.R.styleable#PrintService print-service}&gt;</code> tag. This is
    171      * a sample XML file configuring a print service:
    172      * <pre> &lt;print-service
    173      *     android:vendor="SomeVendor"
    174      *     android:settingsActivity="foo.bar.MySettingsActivity"
    175      *     andorid:addPrintersActivity="foo.bar.MyAddPrintersActivity."
    176      *     . . .
    177      * /&gt;</pre>
    178      * <p>
    179      * For detailed configuration options that can be specified via the meta-data
    180      * refer to {@link android.R.styleable#PrintService android.R.styleable.PrintService}.
    181      * </p>
    182      * <p>
    183      * If you declare a settings or add a printers activity, they have to be exported,
    184      * by setting the {@link android.R.attr#exported} activity attribute to <code>true
    185      * </code>. Also in case you want only the system to be able to start any of these
    186      * activities you can specify that they request the android.permission
    187      * .START_PRINT_SERVICE_CONFIG_ACTIVITY permission by setting the
    188      * {@link android.R.attr#permission} activity attribute.
    189      * </p>
    190      */
    191     public static final String SERVICE_META_DATA = "android.printservice";
    192 
    193     /**
    194      * If you declared an optional activity with advanced print options via the
    195      * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
    196      * attribute, this extra is used to pass in the currently constructed {@link
    197      * PrintJobInfo} to your activity allowing you to modify it. After you are
    198      * done, you must return the modified {@link PrintJobInfo} via the same extra.
    199      * <p>
    200      * You cannot modify the passed in {@link PrintJobInfo} directly, rather you
    201      * should build another one using the {@link PrintJobInfo.Builder} class. You
    202      * can specify any standard properties and add advanced, printer specific,
    203      * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
    204      * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link
    205      * PrintJobInfo.Builder#putAdvancedOption(String, int)
    206      * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options
    207      * are not interpreted by the system, they will not be visible to applications,
    208      * and can only be accessed by your print service via {@link
    209      * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
    210      * and {@link PrintJob#getAdvancedIntOption(String) PrintJob.getAdvancedIntOption(String)}.
    211      * </p>
    212      * <p>
    213      * If the advanced print options activity offers changes to the standard print
    214      * options, you can get the current {@link android.print.PrinterInfo} using the
    215      * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user
    216      * with UI options supported by the current printer. For example, if the current
    217      * printer does not support a given media size, you should not offer it in the
    218      * advanced print options UI.
    219      * </p>
    220      *
    221      * @see #EXTRA_PRINTER_INFO
    222      */
    223     public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
    224 
    225     /**
    226      * If you declared an optional activity with advanced print options via the
    227      * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
    228      * attribute, this extra is used to pass in the currently selected printer's
    229      * {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
    230      *
    231      * @see #EXTRA_PRINT_JOB_INFO
    232      */
    233     public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.EXTRA_PRINTER_INFO";
    234 
    235     private Handler mHandler;
    236 
    237     private IPrintServiceClient mClient;
    238 
    239     private int mLastSessionId = -1;
    240 
    241     private PrinterDiscoverySession mDiscoverySession;
    242 
    243     @Override
    244     protected final void attachBaseContext(Context base) {
    245         super.attachBaseContext(base);
    246         mHandler = new ServiceHandler(base.getMainLooper());
    247     }
    248 
    249     /**
    250      * The system has connected to this service.
    251      */
    252     protected void onConnected() {
    253         /* do nothing */
    254     }
    255 
    256     /**
    257      * The system has disconnected from this service.
    258      */
    259     protected void onDisconnected() {
    260         /* do nothing */
    261     }
    262 
    263     /**
    264      * Callback asking you to create a new {@link PrinterDiscoverySession}.
    265      *
    266      * @see PrinterDiscoverySession
    267      */
    268     protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
    269 
    270     /**
    271      * Called when cancellation of a print job is requested. The service
    272      * should do best effort to fulfill the request. After the cancellation
    273      * is performed, the print job should be marked as cancelled state by
    274      * calling {@link PrintJob#cancel()}.
    275      *
    276      * @param printJob The print job to cancel.
    277      *
    278      * @see PrintJob#cancel() PrintJob.cancel()
    279      * @see PrintJob#isCancelled() PrintJob.isCancelled()
    280      */
    281     protected abstract void onRequestCancelPrintJob(PrintJob printJob);
    282 
    283     /**
    284      * Called when there is a queued print job for one of the printers
    285      * managed by this print service.
    286      *
    287      * @param printJob The new queued print job.
    288      *
    289      * @see PrintJob#isQueued() PrintJob.isQueued()
    290      * @see #getActivePrintJobs()
    291      */
    292     protected abstract void onPrintJobQueued(PrintJob printJob);
    293 
    294     /**
    295      * Gets the active print jobs for the printers managed by this service.
    296      * Active print jobs are ones that are not in a final state, i.e. whose
    297      * state is queued or started.
    298      *
    299      * @return The active print jobs.
    300      *
    301      * @see PrintJob#isQueued() PrintJob.isQueued()
    302      * @see PrintJob#isStarted() PrintJob.isStarted()
    303      */
    304     public final List<PrintJob> getActivePrintJobs() {
    305         throwIfNotCalledOnMainThread();
    306         if (mClient == null) {
    307             return Collections.emptyList();
    308         }
    309         try {
    310             List<PrintJob> printJobs = null;
    311             List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
    312             if (printJobInfos != null) {
    313                 final int printJobInfoCount = printJobInfos.size();
    314                 printJobs = new ArrayList<PrintJob>(printJobInfoCount);
    315                 for (int i = 0; i < printJobInfoCount; i++) {
    316                     printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
    317                 }
    318             }
    319             if (printJobs != null) {
    320                 return printJobs;
    321             }
    322         } catch (RemoteException re) {
    323             Log.e(LOG_TAG, "Error calling getPrintJobs()", re);
    324         }
    325         return Collections.emptyList();
    326     }
    327 
    328     /**
    329      * Generates a global printer id given the printer's locally unique one.
    330      *
    331      * @param localId A locally unique id in the context of your print service.
    332      * @return Global printer id.
    333      */
    334     public final PrinterId generatePrinterId(String localId) {
    335         throwIfNotCalledOnMainThread();
    336         return new PrinterId(new ComponentName(getPackageName(),
    337                 getClass().getName()), localId);
    338     }
    339 
    340     static void throwIfNotCalledOnMainThread() {
    341         if (!Looper.getMainLooper().isCurrentThread()) {
    342             throw new IllegalAccessError("must be called from the main thread");
    343         }
    344     }
    345 
    346     @Override
    347     public final IBinder onBind(Intent intent) {
    348         return new IPrintService.Stub() {
    349             @Override
    350             public void createPrinterDiscoverySession() {
    351                 mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
    352             }
    353 
    354             @Override
    355             public void destroyPrinterDiscoverySession() {
    356                 mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
    357             }
    358 
    359             public void startPrinterDiscovery(List<PrinterId> priorityList) {
    360                 mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
    361                         priorityList).sendToTarget();
    362             }
    363 
    364             @Override
    365             public void stopPrinterDiscovery() {
    366                 mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY);
    367             }
    368 
    369             @Override
    370             public void validatePrinters(List<PrinterId> printerIds) {
    371                 mHandler.obtainMessage(ServiceHandler.MSG_VALIDATE_PRINTERS,
    372                         printerIds).sendToTarget();
    373             }
    374 
    375             @Override
    376             public void startPrinterStateTracking(PrinterId printerId) {
    377                 mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_STATE_TRACKING,
    378                         printerId).sendToTarget();
    379             }
    380 
    381             @Override
    382             public void stopPrinterStateTracking(PrinterId printerId) {
    383                 mHandler.obtainMessage(ServiceHandler.MSG_STOP_PRINTER_STATE_TRACKING,
    384                         printerId).sendToTarget();
    385             }
    386 
    387             @Override
    388             public void setClient(IPrintServiceClient client) {
    389                 mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
    390                         .sendToTarget();
    391             }
    392 
    393             @Override
    394             public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
    395                 mHandler.obtainMessage(ServiceHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
    396                         printJobInfo).sendToTarget();
    397             }
    398 
    399             @Override
    400             public void onPrintJobQueued(PrintJobInfo printJobInfo) {
    401                 mHandler.obtainMessage(ServiceHandler.MSG_ON_PRINTJOB_QUEUED,
    402                         printJobInfo).sendToTarget();
    403             }
    404         };
    405     }
    406 
    407     private final class ServiceHandler extends Handler {
    408         public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
    409         public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
    410         public static final int MSG_START_PRINTER_DISCOVERY = 3;
    411         public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
    412         public static final int MSG_VALIDATE_PRINTERS = 5;
    413         public static final int MSG_START_PRINTER_STATE_TRACKING = 6;
    414         public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7;
    415         public static final int MSG_ON_PRINTJOB_QUEUED = 8;
    416         public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 9;
    417         public static final int MSG_SET_CLEINT = 10;
    418 
    419         public ServiceHandler(Looper looper) {
    420             super(looper, null, true);
    421         }
    422 
    423         @Override
    424         @SuppressWarnings("unchecked")
    425         public void handleMessage(Message message) {
    426             final int action = message.what;
    427             switch (action) {
    428                 case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
    429                     if (DEBUG) {
    430                         Log.i(LOG_TAG, "MSG_CREATE_PRINTER_DISCOVERY_SESSION "
    431                                 + getPackageName());
    432                     }
    433                     PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
    434                     if (session == null) {
    435                         throw new NullPointerException("session cannot be null");
    436                     }
    437                     if (session.getId() == mLastSessionId) {
    438                         throw new IllegalStateException("cannot reuse session instances");
    439                     }
    440                     mDiscoverySession = session;
    441                     mLastSessionId = session.getId();
    442                     session.setObserver(mClient);
    443                 } break;
    444 
    445                 case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
    446                     if (DEBUG) {
    447                         Log.i(LOG_TAG, "MSG_DESTROY_PRINTER_DISCOVERY_SESSION "
    448                                 + getPackageName());
    449                     }
    450                     if (mDiscoverySession != null) {
    451                         mDiscoverySession.destroy();
    452                         mDiscoverySession = null;
    453                     }
    454                 } break;
    455 
    456                 case MSG_START_PRINTER_DISCOVERY: {
    457                     if (DEBUG) {
    458                         Log.i(LOG_TAG, "MSG_START_PRINTER_DISCOVERY "
    459                                 + getPackageName());
    460                     }
    461                     if (mDiscoverySession != null) {
    462                         List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
    463                         mDiscoverySession.startPrinterDiscovery(priorityList);
    464                     }
    465                 } break;
    466 
    467                 case MSG_STOP_PRINTER_DISCOVERY: {
    468                     if (DEBUG) {
    469                         Log.i(LOG_TAG, "MSG_STOP_PRINTER_DISCOVERY "
    470                                 + getPackageName());
    471                     }
    472                     if (mDiscoverySession != null) {
    473                         mDiscoverySession.stopPrinterDiscovery();
    474                     }
    475                 } break;
    476 
    477                 case MSG_VALIDATE_PRINTERS: {
    478                     if (DEBUG) {
    479                         Log.i(LOG_TAG, "MSG_VALIDATE_PRINTERS "
    480                                 + getPackageName());
    481                     }
    482                     if (mDiscoverySession != null) {
    483                         List<PrinterId> printerIds = (List<PrinterId>) message.obj;
    484                         mDiscoverySession.validatePrinters(printerIds);
    485                     }
    486                 } break;
    487 
    488                 case MSG_START_PRINTER_STATE_TRACKING: {
    489                     if (DEBUG) {
    490                         Log.i(LOG_TAG, "MSG_START_PRINTER_STATE_TRACKING "
    491                                 + getPackageName());
    492                     }
    493                     if (mDiscoverySession != null) {
    494                         PrinterId printerId = (PrinterId) message.obj;
    495                         mDiscoverySession.startPrinterStateTracking(printerId);
    496                     }
    497                 } break;
    498 
    499                 case MSG_STOP_PRINTER_STATE_TRACKING: {
    500                     if (DEBUG) {
    501                         Log.i(LOG_TAG, "MSG_STOP_PRINTER_STATE_TRACKING "
    502                                 + getPackageName());
    503                     }
    504                     if (mDiscoverySession != null) {
    505                         PrinterId printerId = (PrinterId) message.obj;
    506                         mDiscoverySession.stopPrinterStateTracking(printerId);
    507                     }
    508                 } break;
    509 
    510                 case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
    511                     if (DEBUG) {
    512                         Log.i(LOG_TAG, "MSG_ON_REQUEST_CANCEL_PRINTJOB "
    513                                 + getPackageName());
    514                     }
    515                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
    516                     onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
    517                 } break;
    518 
    519                 case MSG_ON_PRINTJOB_QUEUED: {
    520                     if (DEBUG) {
    521                         Log.i(LOG_TAG, "MSG_ON_PRINTJOB_QUEUED "
    522                                 + getPackageName());
    523                     }
    524                     PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
    525                     if (DEBUG) {
    526                         Log.i(LOG_TAG, "Queued: " + printJobInfo);
    527                     }
    528                     onPrintJobQueued(new PrintJob(printJobInfo, mClient));
    529                 } break;
    530 
    531                 case MSG_SET_CLEINT: {
    532                     if (DEBUG) {
    533                         Log.i(LOG_TAG, "MSG_SET_CLEINT "
    534                                 + getPackageName());
    535                     }
    536                     mClient = (IPrintServiceClient) message.obj;
    537                     if (mClient != null) {
    538                         onConnected();
    539                      } else {
    540                         onDisconnected();
    541                      }
    542                 } break;
    543 
    544                 default: {
    545                     throw new IllegalArgumentException("Unknown message: " + action);
    546                 }
    547             }
    548         }
    549     }
    550 }
    551