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