• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
PrinterDiscoverySession()107     public PrinterDiscoverySession() {
108         mId = sIdCounter++;
109     }
110 
setObserver(IPrintServiceClient observer)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 
getId()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      */
getPrinters()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      */
addPrinters(@onNull List<PrinterInfo> printers)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      */
removePrinters(@onNull List<PrinterId> printerIds)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 
sendOutOfDiscoveryPeriodPrinterChanges()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      */
onStartPrinterDiscovery(@onNull List<PrinterId> priorityList)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      */
onStopPrinterDiscovery()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      */
onValidatePrinters(@onNull List<PrinterId> printerIds)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      */
onStartPrinterStateTracking(@onNull PrinterId printerId)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      */
onRequestCustomPrinterIcon(@onNull PrinterId printerId, @NonNull CancellationSignal cancellationSignal, @NonNull CustomPrinterIconCallback callback)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      */
onStopPrinterStateTracking(@onNull PrinterId printerId)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      */
getTrackedPrinters()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      */
onDestroy()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      */
isDestroyed()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      */
isPrinterDiscoveryStarted()492     public final boolean isPrinterDiscoveryStarted() {
493         PrintService.throwIfNotCalledOnMainThread();
494         return mIsDiscoveryStarted;
495     }
496 
startPrinterDiscovery(@onNull List<PrinterId> priorityList)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 
stopPrinterDiscovery()508     void stopPrinterDiscovery() {
509         if (!mIsDestroyed) {
510             mIsDiscoveryStarted = false;
511             onStopPrinterDiscovery();
512         }
513     }
514 
validatePrinters(@onNull List<PrinterId> printerIds)515     void validatePrinters(@NonNull List<PrinterId> printerIds) {
516         if (!mIsDestroyed && mObserver != null) {
517             onValidatePrinters(printerIds);
518         }
519     }
520 
startPrinterStateTracking(@onNull PrinterId printerId)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      */
requestCustomPrinterIcon(@onNull PrinterId printerId)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 
stopPrinterStateTracking(@onNull PrinterId printerId)543     void stopPrinterStateTracking(@NonNull PrinterId printerId) {
544         if (!mIsDestroyed && mObserver != null
545                 && mTrackedPrinters.remove(printerId)) {
546             onStopPrinterStateTracking(printerId);
547         }
548     }
549 
destroy()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