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.content.pm.ParceledListSlice;
     20 import android.os.RemoteException;
     21 import android.print.PrinterCapabilitiesInfo;
     22 import android.print.PrinterId;
     23 import android.print.PrinterInfo;
     24 import android.util.ArrayMap;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.Collections;
     29 import java.util.List;
     30 
     31 /**
     32  * This class encapsulates the interaction between a print service and the
     33  * system during printer discovery. During printer discovery you are responsible
     34  * for adding discovered printers, removing previously added printers that
     35  * disappeared, and updating already added printers.
     36  * <p>
     37  * During the lifetime of this session you may be asked to start and stop
     38  * performing printer discovery multiple times. You will receive a call to {@link
     39  * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer
     40  * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()}
     41  * to stop printer discovery. When the system is no longer interested in printers
     42  * discovered by this session you will receive a call to {@link #onDestroy()} at
     43  * which point the system will no longer call into the session and all the session
     44  * methods will do nothing.
     45  * </p>
     46  * <p>
     47  * Discovered printers are added by invoking {@link
     48  * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared are
     49  * removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. Added
     50  * printers whose properties or capabilities changed are updated through a call to
     51  * {@link PrinterDiscoverySession#addPrinters(List)}. The printers added in this
     52  * session can be acquired via {@link #getPrinters()} where the returned printers
     53  * will be an up-to-date snapshot of the printers that you reported during the
     54  * session. Printers are <strong>not</strong> persisted across sessions.
     55  * </p>
     56  * <p>
     57  * The system will make a call to {@link #onValidatePrinters(List)} if you
     58  * need to update some printers. It is possible that you add a printer without
     59  * specifying its capabilities. This enables you to avoid querying all discovered
     60  * printers for their capabilities, rather querying the capabilities of a printer
     61  * only if necessary. For example, the system will request that you update a printer
     62  * if it gets selected by the user. When validating printers you do not need to
     63  * provide the printers' capabilities but may do so.
     64  * </p>
     65  * <p>
     66  * If the system is interested in being constantly updated for the state of a
     67  * printer you will receive a call to {@link #onStartPrinterStateTracking(PrinterId)}
     68  * after which you will have to do a best effort to keep the system updated for
     69  * changes in the printer state and capabilities. You also <strong>must</strong>
     70  * update the printer capabilities if you did not provide them when adding it, or
     71  * the printer will be ignored. When the system is no longer interested in getting
     72  * updates for a printer you will receive a call to {@link #onStopPrinterStateTracking(
     73  * PrinterId)}.
     74  * </p>
     75  * <p>
     76  * <strong>Note: </strong> All callbacks in this class are executed on the main
     77  * application thread. You also have to invoke any method of this class on the main
     78  * application thread.
     79  * </p>
     80  */
     81 public abstract class PrinterDiscoverySession {
     82     private static final String LOG_TAG = "PrinterDiscoverySession";
     83 
     84     private static int sIdCounter = 0;
     85 
     86     private final int mId;
     87 
     88     private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
     89             new ArrayMap<PrinterId, PrinterInfo>();
     90 
     91     private final List<PrinterId> mTrackedPrinters =
     92             new ArrayList<PrinterId>();
     93 
     94     private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters;
     95 
     96     private IPrintServiceClient mObserver;
     97 
     98     private boolean mIsDestroyed;
     99 
    100     private boolean mIsDiscoveryStarted;
    101 
    102     /**
    103      * Constructor.
    104      */
    105     public PrinterDiscoverySession() {
    106         mId = sIdCounter++;
    107     }
    108 
    109     void setObserver(IPrintServiceClient observer) {
    110         mObserver = observer;
    111         // If some printers were added in the method that
    112         // created the session, send them over.
    113         if (!mPrinters.isEmpty()) {
    114             try {
    115                 mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(getPrinters()));
    116             } catch (RemoteException re) {
    117                 Log.e(LOG_TAG, "Error sending added printers", re);
    118             }
    119         }
    120     }
    121 
    122     int getId() {
    123         return mId;
    124     }
    125 
    126     /**
    127      * Gets the printers reported in this session. For example, if you add two
    128      * printers and remove one of them, the returned list will contain only
    129      * the printer that was added but not removed.
    130      * <p>
    131      * <strong>Note: </strong> Calls to this method after the session is
    132      * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
    133      * </p>
    134      *
    135      * @return The printers.
    136      *
    137      * @see #addPrinters(List)
    138      * @see #removePrinters(List)
    139      * @see #isDestroyed()
    140      */
    141     public final List<PrinterInfo> getPrinters() {
    142         PrintService.throwIfNotCalledOnMainThread();
    143         if (mIsDestroyed) {
    144             return Collections.emptyList();
    145         }
    146         return new ArrayList<PrinterInfo>(mPrinters.values());
    147     }
    148 
    149     /**
    150      * Adds discovered printers. Adding an already added printer updates it.
    151      * Removed printers can be added again. You can call this method multiple
    152      * times during the life of this session. Duplicates will be ignored.
    153      * <p>
    154      * <strong>Note: </strong> Calls to this method after the session is
    155      * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
    156      * </p>
    157      *
    158      * @param printers The printers to add.
    159      *
    160      * @see #removePrinters(List)
    161      * @see #getPrinters()
    162      * @see #isDestroyed()
    163      */
    164     public final void addPrinters(List<PrinterInfo> printers) {
    165         PrintService.throwIfNotCalledOnMainThread();
    166 
    167         // If the session is destroyed - nothing do to.
    168         if (mIsDestroyed) {
    169             Log.w(LOG_TAG, "Not adding printers - session destroyed.");
    170             return;
    171         }
    172 
    173         if (mIsDiscoveryStarted) {
    174             // If during discovery, add the new printers and send them.
    175             List<PrinterInfo> addedPrinters = null;
    176             final int addedPrinterCount = printers.size();
    177             for (int i = 0; i < addedPrinterCount; i++) {
    178                 PrinterInfo addedPrinter = printers.get(i);
    179                 PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter);
    180                 if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) {
    181                     if (addedPrinters == null) {
    182                         addedPrinters = new ArrayList<PrinterInfo>();
    183                     }
    184                     addedPrinters.add(addedPrinter);
    185                 }
    186             }
    187 
    188             // Send the added printers, if such.
    189             if (addedPrinters != null) {
    190                 try {
    191                     mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters));
    192                 } catch (RemoteException re) {
    193                     Log.e(LOG_TAG, "Error sending added printers", re);
    194                 }
    195             }
    196         } else {
    197             // Remember the last sent printers if needed.
    198             if (mLastSentPrinters == null) {
    199                 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
    200             }
    201 
    202             // Update the printers.
    203             final int addedPrinterCount = printers.size();
    204             for (int i = 0; i < addedPrinterCount; i++) {
    205                 PrinterInfo addedPrinter = printers.get(i);
    206                 if (mPrinters.get(addedPrinter.getId()) == null) {
    207                     mPrinters.put(addedPrinter.getId(), addedPrinter);
    208                 }
    209             }
    210         }
    211     }
    212 
    213     /**
    214      * Removes added printers. Removing an already removed or never added
    215      * printer has no effect. Removed printers can be added again. You can
    216      * call this method multiple times during the lifetime of this session.
    217      * <p>
    218      * <strong>Note: </strong> Calls to this method after the session is
    219      * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
    220      * </p>
    221      *
    222      * @param printerIds The ids of the removed printers.
    223      *
    224      * @see #addPrinters(List)
    225      * @see #getPrinters()
    226      * @see #isDestroyed()
    227      */
    228     public final void removePrinters(List<PrinterId> printerIds) {
    229         PrintService.throwIfNotCalledOnMainThread();
    230 
    231         // If the session is destroyed - nothing do to.
    232         if (mIsDestroyed) {
    233             Log.w(LOG_TAG, "Not removing printers - session destroyed.");
    234             return;
    235         }
    236 
    237         if (mIsDiscoveryStarted) {
    238             // If during discovery, remove existing printers and send them.
    239             List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>();
    240             final int removedPrinterIdCount = printerIds.size();
    241             for (int i = 0; i < removedPrinterIdCount; i++) {
    242                 PrinterId removedPrinterId = printerIds.get(i);
    243                 if (mPrinters.remove(removedPrinterId) != null) {
    244                     removedPrinterIds.add(removedPrinterId);
    245                 }
    246             }
    247 
    248             // Send the removed printers, if such.
    249             if (!removedPrinterIds.isEmpty()) {
    250                 try {
    251                     mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>(
    252                             removedPrinterIds));
    253                 } catch (RemoteException re) {
    254                     Log.e(LOG_TAG, "Error sending removed printers", re);
    255                 }
    256             }
    257         } else {
    258             // Remember the last sent printers if needed.
    259             if (mLastSentPrinters == null) {
    260                 mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
    261             }
    262 
    263             // Update the printers.
    264             final int removedPrinterIdCount = printerIds.size();
    265             for (int i = 0; i < removedPrinterIdCount; i++) {
    266                 PrinterId removedPrinterId = printerIds.get(i);
    267                 mPrinters.remove(removedPrinterId);
    268             }
    269         }
    270     }
    271 
    272     private void sendOutOfDiscoveryPeriodPrinterChanges() {
    273         // Noting changed since the last discovery period - nothing to do.
    274         if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) {
    275             mLastSentPrinters = null;
    276             return;
    277         }
    278 
    279         // Determine the added printers.
    280         List<PrinterInfo> addedPrinters = null;
    281         for (PrinterInfo printer : mPrinters.values()) {
    282             PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId());
    283             if (sentPrinter == null || !sentPrinter.equals(printer)) {
    284                 if (addedPrinters == null) {
    285                     addedPrinters = new ArrayList<PrinterInfo>();
    286                 }
    287                 addedPrinters.add(printer);
    288             }
    289         }
    290 
    291         // Send the added printers, if such.
    292         if (addedPrinters != null) {
    293             try {
    294                 mObserver.onPrintersAdded(new ParceledListSlice<PrinterInfo>(addedPrinters));
    295             } catch (RemoteException re) {
    296                 Log.e(LOG_TAG, "Error sending added printers", re);
    297             }
    298         }
    299 
    300         // Determine the removed printers.
    301         List<PrinterId> removedPrinterIds = null;
    302         for (PrinterInfo sentPrinter : mLastSentPrinters.values()) {
    303             if (!mPrinters.containsKey(sentPrinter.getId())) {
    304                 if (removedPrinterIds == null) {
    305                     removedPrinterIds = new ArrayList<PrinterId>();
    306                 }
    307                 removedPrinterIds.add(sentPrinter.getId());
    308             }
    309         }
    310 
    311         // Send the removed printers, if such.
    312         if (removedPrinterIds != null) {
    313             try {
    314                 mObserver.onPrintersRemoved(new ParceledListSlice<PrinterId>(removedPrinterIds));
    315             } catch (RemoteException re) {
    316                 Log.e(LOG_TAG, "Error sending removed printers", re);
    317             }
    318         }
    319 
    320         mLastSentPrinters = null;
    321     }
    322 
    323     /**
    324      * Callback asking you to start printer discovery. Discovered printers should be
    325      * added via calling {@link #addPrinters(List)}. Added printers that disappeared
    326      * should be removed via calling {@link #removePrinters(List)}. Added printers
    327      * whose properties or capabilities changed should be updated via calling {@link
    328      * #addPrinters(List)}. You will receive a call to {@link #onStopPrinterDiscovery()}
    329      * when you should stop printer discovery.
    330      * <p>
    331      * During the lifetime of this session all printers that are known to your print
    332      * service have to be added. The system does not retain any printers across sessions.
    333      * However, if you were asked to start and then stop performing printer discovery
    334      * in this session, then a subsequent discovering should not re-discover already
    335      * discovered printers. You can get the printers reported during this session by
    336      * calling {@link #getPrinters()}.
    337      * </p>
    338      * <p>
    339      * <strong>Note: </strong>You are also given a list of printers whose availability
    340      * has to be checked first. For example, these printers could be the user's favorite
    341      * ones, therefore they have to be verified first. You do <strong>not need</strong>
    342      * to provide the capabilities of the printers, rather verify whether they exist
    343      * similarly to {@link #onValidatePrinters(List)}.
    344      * </p>
    345      *
    346      * @param priorityList The list of printers to validate first. Never null.
    347      *
    348      * @see #onStopPrinterDiscovery()
    349      * @see #addPrinters(List)
    350      * @see #removePrinters(List)
    351      * @see #isPrinterDiscoveryStarted()
    352      */
    353     public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
    354 
    355     /**
    356      * Callback notifying you that you should stop printer discovery.
    357      *
    358      * @see #onStartPrinterDiscovery(List)
    359      * @see #isPrinterDiscoveryStarted()
    360      */
    361     public abstract void onStopPrinterDiscovery();
    362 
    363     /**
    364      * Callback asking you to validate that the given printers are valid, that
    365      * is they exist. You are responsible for checking whether these printers
    366      * exist and for the ones that do exist notify the system via calling
    367      * {@link #addPrinters(List)}.
    368      * <p>
    369      * <strong>Note: </strong> You are <strong>not required</strong> to provide
    370      * the printer capabilities when updating the printers that do exist.
    371      * <p>
    372      *
    373      * @param printerIds The printers to validate.
    374      *
    375      * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
    376      *      PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
    377      */
    378     public abstract void onValidatePrinters(List<PrinterId> printerIds);
    379 
    380     /**
    381      * Callback asking you to start tracking the state of a printer. Tracking
    382      * the state means that you should do a best effort to observe the state
    383      * of this printer and notify the system if that state changes via calling
    384      * {@link #addPrinters(List)}.
    385      * <p>
    386      * <strong>Note: </strong> A printer can be initially added without its
    387      * capabilities to avoid polling printers that the user will not select.
    388      * However, after this method is called you are expected to update the
    389      * printer <strong>including</strong> its capabilities. Otherwise, the
    390      * printer will be ignored.
    391      * <p>
    392      * <p>
    393      * A scenario when you may be requested to track a printer's state is if
    394      * the user selects that printer and the system has to present print
    395      * options UI based on the printer's capabilities. In this case the user
    396      * should be promptly informed if, for example, the printer becomes
    397      * unavailable.
    398      * </p>
    399      *
    400      * @param printerId The printer to start tracking.
    401      *
    402      * @see #onStopPrinterStateTracking(PrinterId)
    403      * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
    404      *      PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
    405      */
    406     public abstract void onStartPrinterStateTracking(PrinterId printerId);
    407 
    408     /**
    409      * Callback asking you to stop tracking the state of a printer. The passed
    410      * in printer id is the one for which you received a call to {@link
    411      * #onStartPrinterStateTracking(PrinterId)}.
    412      *
    413      * @param printerId The printer to stop tracking.
    414      *
    415      * @see #onStartPrinterStateTracking(PrinterId)
    416      */
    417     public abstract void onStopPrinterStateTracking(PrinterId printerId);
    418 
    419     /**
    420      * Gets the printers that should be tracked. These are printers that are
    421      * important to the user and for which you received a call to {@link
    422      * #onStartPrinterStateTracking(PrinterId)} asking you to observer their
    423      * state and reporting it to the system via {@link #addPrinters(List)}.
    424      * You will receive a call to {@link #onStopPrinterStateTracking(PrinterId)}
    425      * if you should stop tracking a printer.
    426      * <p>
    427      * <strong>Note: </strong> Calls to this method after the session is
    428      * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
    429      * </p>
    430      *
    431      * @return The printers.
    432      *
    433      * @see #onStartPrinterStateTracking(PrinterId)
    434      * @see #onStopPrinterStateTracking(PrinterId)
    435      * @see #isDestroyed()
    436      */
    437     public final List<PrinterId> getTrackedPrinters() {
    438         PrintService.throwIfNotCalledOnMainThread();
    439         if (mIsDestroyed) {
    440             return Collections.emptyList();
    441         }
    442         return new ArrayList<PrinterId>(mTrackedPrinters);
    443     }
    444 
    445     /**
    446      * Notifies you that the session is destroyed. After this callback is invoked
    447      * any calls to the methods of this class will be ignored, {@link #isDestroyed()}
    448      * will return true and you will also no longer receive callbacks.
    449      *
    450      * @see #isDestroyed()
    451      */
    452     public abstract void onDestroy();
    453 
    454     /**
    455      * Gets whether the session is destroyed.
    456      *
    457      * @return Whether the session is destroyed.
    458      *
    459      * @see #onDestroy()
    460      */
    461     public final boolean isDestroyed() {
    462         PrintService.throwIfNotCalledOnMainThread();
    463         return mIsDestroyed;
    464     }
    465 
    466     /**
    467      * Gets whether printer discovery is started.
    468      *
    469      * @return Whether printer discovery is destroyed.
    470      *
    471      * @see #onStartPrinterDiscovery(List)
    472      * @see #onStopPrinterDiscovery()
    473      */
    474     public final boolean isPrinterDiscoveryStarted() {
    475         PrintService.throwIfNotCalledOnMainThread();
    476         return mIsDiscoveryStarted;
    477     }
    478 
    479     void startPrinterDiscovery(List<PrinterId> priorityList) {
    480         if (!mIsDestroyed) {
    481             mIsDiscoveryStarted = true;
    482             sendOutOfDiscoveryPeriodPrinterChanges();
    483             if (priorityList == null) {
    484                 priorityList = Collections.emptyList();
    485             }
    486             onStartPrinterDiscovery(priorityList);
    487         }
    488     }
    489 
    490     void stopPrinterDiscovery() {
    491         if (!mIsDestroyed) {
    492             mIsDiscoveryStarted = false;
    493             onStopPrinterDiscovery();
    494         }
    495     }
    496 
    497     void validatePrinters(List<PrinterId> printerIds) {
    498         if (!mIsDestroyed && mObserver != null) {
    499             onValidatePrinters(printerIds);
    500         }
    501     }
    502 
    503     void startPrinterStateTracking(PrinterId printerId) {
    504         if (!mIsDestroyed && mObserver != null
    505                 && !mTrackedPrinters.contains(printerId)) {
    506             mTrackedPrinters.add(printerId);
    507             onStartPrinterStateTracking(printerId);
    508         }
    509     }
    510 
    511     void stopPrinterStateTracking(PrinterId printerId) {
    512         if (!mIsDestroyed && mObserver != null
    513                 && mTrackedPrinters.remove(printerId)) {
    514             onStopPrinterStateTracking(printerId);
    515         }
    516     }
    517 
    518     void destroy() {
    519         if (!mIsDestroyed) {
    520             mIsDestroyed = true;
    521             mIsDiscoveryStarted = false;
    522             mPrinters.clear();
    523             mLastSentPrinters = null;
    524             mObserver = null;
    525             onDestroy();
    526         }
    527     }
    528 }
    529