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      */
PrinterDiscoverySession()105     public PrinterDiscoverySession() {
106         mId = sIdCounter++;
107     }
108 
setObserver(IPrintServiceClient observer)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 
getId()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      */
getPrinters()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      */
addPrinters(List<PrinterInfo> printers)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      */
removePrinters(List<PrinterId> printerIds)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 
sendOutOfDiscoveryPeriodPrinterChanges()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      */
onStartPrinterDiscovery(List<PrinterId> priorityList)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      */
onStopPrinterDiscovery()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      */
onValidatePrinters(List<PrinterId> printerIds)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      */
onStartPrinterStateTracking(PrinterId printerId)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      */
onStopPrinterStateTracking(PrinterId printerId)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      */
getTrackedPrinters()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      */
onDestroy()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      */
isDestroyed()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      */
isPrinterDiscoveryStarted()474     public final boolean isPrinterDiscoveryStarted() {
475         PrintService.throwIfNotCalledOnMainThread();
476         return mIsDiscoveryStarted;
477     }
478 
startPrinterDiscovery(List<PrinterId> priorityList)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 
stopPrinterDiscovery()490     void stopPrinterDiscovery() {
491         if (!mIsDestroyed) {
492             mIsDiscoveryStarted = false;
493             onStopPrinterDiscovery();
494         }
495     }
496 
validatePrinters(List<PrinterId> printerIds)497     void validatePrinters(List<PrinterId> printerIds) {
498         if (!mIsDestroyed && mObserver != null) {
499             onValidatePrinters(printerIds);
500         }
501     }
502 
startPrinterStateTracking(PrinterId printerId)503     void startPrinterStateTracking(PrinterId printerId) {
504         if (!mIsDestroyed && mObserver != null
505                 && !mTrackedPrinters.contains(printerId)) {
506             mTrackedPrinters.add(printerId);
507             onStartPrinterStateTracking(printerId);
508         }
509     }
510 
stopPrinterStateTracking(PrinterId printerId)511     void stopPrinterStateTracking(PrinterId printerId) {
512         if (!mIsDestroyed && mObserver != null
513                 && mTrackedPrinters.remove(printerId)) {
514             onStopPrinterStateTracking(printerId);
515         }
516     }
517 
destroy()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