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