PrinterDiscoverySession.java revision 798bed6cc7d273e72b0253288605db9cd2b57740
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.content.Context;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.RemoteException;
24import android.print.IPrinterDiscoverySessionController;
25import android.print.IPrinterDiscoverySessionObserver;
26import android.print.PrinterId;
27import android.print.PrinterInfo;
28import android.util.Log;
29
30import java.lang.ref.WeakReference;
31import 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 already added printers that
37 * disappeared, and updating already added printers.
38 * <p>
39 * The opening of the session is announced by a call to {@link
40 * PrinterDiscoverySession#onOpen(List)} at which point you should start printer
41 * discovery. The closing of the session is announced by a call to {@link
42 * PrinterDiscoverySession#onClose()} at which point you should stop printer
43 * discovery. Discovered printers are added by invoking {@link
44 * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared
45 * are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}.
46 * Added printers whose properties or capabilities changed are updated through
47 * a call to {@link PrinterDiscoverySession#updatePrinters(List)}.
48 * </p>
49 * <p>
50 * The system will make a call to
51 * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you
52 * need to update a given printer. It is possible that you add a printer without
53 * specifying its capabilities. This enables you to avoid querying all
54 * discovered printers for their capabilities, rather querying the capabilities
55 * of a printer only if necessary. For example, the system will require that you
56 * update a printer if it gets selected by the user. If you did not report the
57 * printer capabilities when adding it, you must do so after the system requests
58 * a printer update. Otherwise, the printer will be ignored.
59 * </p>
60 * <p>
61 * During printer discovery all printers that are known to your print service
62 * have to be added. The system does not retain any printers from previous
63 * sessions.
64 * </p>
65 */
66public abstract class PrinterDiscoverySession {
67    private static final String LOG_TAG = "PrinterDiscoverySession";
68
69    private static int sIdCounter = 0;
70
71    private final Object mLock = new Object();
72
73    private final Handler mHandler;
74
75    private final int mId;
76
77    private IPrinterDiscoverySessionController mController;
78
79    private IPrinterDiscoverySessionObserver mObserver;
80
81    /**
82     * Constructor.
83     *
84     * @param context A context instance.
85     */
86    public PrinterDiscoverySession(Context context) {
87        mId = sIdCounter++;
88        mHandler = new SessionHandler(context.getMainLooper());
89        mController = new PrinterDiscoverySessionController(this);
90    }
91
92    void setObserver(IPrinterDiscoverySessionObserver observer) {
93        synchronized (mLock) {
94            mObserver = observer;
95            try {
96                mObserver.setController(mController);
97            } catch (RemoteException re) {
98                Log.e(LOG_TAG, "Error setting session controller", re);
99            }
100        }
101    }
102
103    int getId() {
104        return mId;
105    }
106
107    /**
108     * Adds discovered printers. Adding an already added printer has no effect.
109     * Removed printers can be added again. You can call this method multiple
110     * times during printer discovery.
111     * <p>
112     * <strong>Note: </strong> Calling this method when the session is closed,
113     * which is if {@link #isClosed()} returns true, will throw an {@link
114     * IllegalStateException}.
115     * </p>
116     *
117     * @param printers The printers to add.
118     *
119     * @see #removePrinters(List)
120     * @see #updatePrinters(List)
121     * @see #isClosed()
122     */
123    public final void addPrinters(List<PrinterInfo> printers) {
124        final IPrinterDiscoverySessionObserver observer;
125        synchronized (mLock) {
126            throwIfClosedLocked();
127            observer = mObserver;
128        }
129        if (observer != null) {
130            try {
131                observer.onPrintersAdded(printers);
132            } catch (RemoteException re) {
133                Log.e(LOG_TAG, "Error adding printers", re);
134            }
135        }
136    }
137
138    /**
139     * Removes added printers. Removing an already removed or never added
140     * printer has no effect. Removed printers can be added again. You
141     * can call this method multiple times during printer discovery.
142     * <p>
143     * <strong>Note: </strong> Calling this method when the session is closed,
144     * which is if {@link #isClosed()} returns true, will throw an {@link
145     * IllegalStateException}.
146     * </p>
147     *
148     * @param printerIds The ids of the removed printers.
149     *
150     * @see #addPrinters(List)
151     * @see #updatePrinters(List)
152     * @see #isClosed()
153     */
154    public final void removePrinters(List<PrinterId> printerIds) {
155        final IPrinterDiscoverySessionObserver observer;
156        synchronized (mLock) {
157            throwIfClosedLocked();
158            observer = mObserver;
159        }
160        if (observer != null) {
161            try {
162                observer.onPrintersRemoved(printerIds);
163            } catch (RemoteException re) {
164                Log.e(LOG_TAG, "Error removing printers", re);
165            }
166        }
167    }
168
169    /**
170     * Updates added printers. Updating a printer that was not added or that
171     * was removed has no effect. You can call this method multiple times
172     * during printer discovery.
173     * <p>
174     * <strong>Note: </strong> Calling this method when the session is closed,
175     * which is if {@link #isClosed()} returns true, will throw an {@link
176     * IllegalStateException}.
177     * </p>
178     *
179     * @param printers The printers to update.
180     *
181     * @see #addPrinters(List)
182     * @see #removePrinters(List)
183     * @see #isClosed()
184     */
185    public final void updatePrinters(List<PrinterInfo> printers) {
186        final IPrinterDiscoverySessionObserver observer;
187        synchronized (mLock) {
188            throwIfClosedLocked();
189            observer = mObserver;
190        }
191        if (observer != null) {
192            try {
193                observer.onPrintersUpdated(printers);
194            } catch (RemoteException re) {
195                Log.e(LOG_TAG, "Error updating printers", re);
196            }
197        }
198    }
199
200    /**
201     * Callback notifying you that the session is open and you should start
202     * printer discovery. Discovered printers should be added via calling
203     * {@link #addPrinters(List)}. Added printers that disappeared should be
204     * removed via calling {@link #removePrinters(List)}. Added printers whose
205     * properties or capabilities changes should be updated via calling {@link
206     * #updatePrinters(List)}. When the session is closed you will receive a
207     * call to {@link #onClose()}.
208     * <p>
209     * During printer discovery all printers that are known to your print
210     * service have to be added. The system does not retain any printers from
211     * previous sessions.
212     * </p>
213     * <p>
214     * <strong>Note: </strong>You are also given a list of printers whose
215     * availability has to be checked first. For example, these printers could
216     * be the user's favorite ones, therefore they have to be verified first.
217     * </p>
218     *
219     * @see #onClose()
220     * @see #isClosed()
221     * @see #addPrinters(List)
222     * @see #removePrinters(List)
223     * @see #updatePrinters(List)
224     */
225    public abstract void onOpen(List<PrinterId> priorityList);
226
227    /**
228     * Callback notifying you that the session is closed and you should stop
229     * printer discovery. After the session is closed and any attempt to call
230     * any of its methods will throw an exception. Whether a session is closed
231     * can be checked by calling {@link #isClosed()}. Once the session is closed
232     * it will never be opened again.
233     *
234     * @see #onOpen(List)
235     * @see #isClosed()
236     * @see #addPrinters(List)
237     * @see #removePrinters(List)
238     * @see #updatePrinters(List)
239     */
240    public abstract void onClose();
241
242    /**
243     * Requests that you update a printer. You are responsible for updating
244     * the printer by also reporting its capabilities via calling {@link
245     * #updatePrinters(List)}.
246     * <p>
247     * <strong>Note: </strong> A printer can be initially added without its
248     * capabilities to avoid polling printers that the user will not select.
249     * However, after this method is called you are expected to update the
250     * printer <strong>including</strong> its capabilities. Otherwise, the
251     * printer will be ignored.
252     * <p>
253     * A scenario when you may be requested to update a printer is if the user
254     * selects it and the system has to present print options UI based on the
255     * printer's capabilities.
256     * </p>
257     *
258     * @param printerId The printer id.
259     *
260     * @see #updatePrinters(List)
261     * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
262     *      PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
263     */
264    public abstract void onRequestPrinterUpdate(PrinterId printerId);
265
266    /**
267     * Gets whether this session is closed.
268     *
269     * @return Whether the session is closed.
270     */
271    public final boolean isClosed() {
272        synchronized (mLock) {
273            return (mController == null && mObserver == null);
274        }
275    }
276
277    void close() {
278        synchronized (mLock) {
279            throwIfClosedLocked();
280            mController = null;
281            mObserver = null;
282        }
283    }
284
285    private void throwIfClosedLocked() {
286        if (isClosed()) {
287            throw new IllegalStateException("Session is closed");
288        }
289    }
290
291    private final class SessionHandler extends Handler {
292        public static final int MSG_OPEN = 1;
293        public static final int MSG_CLOSE = 2;
294        public static final int MSG_REQUEST_PRINTER_UPDATE = 3;
295
296        public SessionHandler(Looper looper) {
297            super(looper, null, true);
298        }
299
300        @Override
301        @SuppressWarnings("unchecked")
302        public void handleMessage(Message message) {
303            switch (message.what) {
304                case MSG_OPEN: {
305                    List<PrinterId> priorityList = (List<PrinterId>) message.obj;
306                    onOpen(priorityList);
307                } break;
308
309                case MSG_CLOSE: {
310                    onClose();
311                    close();
312                } break;
313
314                case MSG_REQUEST_PRINTER_UPDATE: {
315                    PrinterId printerId = (PrinterId) message.obj;
316                    onRequestPrinterUpdate(printerId);
317                } break;
318            }
319        }
320    }
321
322    private static final class PrinterDiscoverySessionController extends
323            IPrinterDiscoverySessionController.Stub {
324        private final WeakReference<PrinterDiscoverySession> mWeakSession;
325
326        public PrinterDiscoverySessionController(PrinterDiscoverySession session) {
327            mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
328        }
329
330        @Override
331        public void open(List<PrinterId> priorityList) {
332            PrinterDiscoverySession session = mWeakSession.get();
333            if (session != null) {
334                session.mHandler.obtainMessage(SessionHandler.MSG_OPEN,
335                        priorityList).sendToTarget();
336            }
337        }
338
339        @Override
340        public void close() {
341            PrinterDiscoverySession session = mWeakSession.get();
342            if (session != null) {
343                session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE);
344            }
345        }
346
347        @Override
348        public void requestPrinterUpdate(PrinterId printerId) {
349            PrinterDiscoverySession session = mWeakSession.get();
350            if (session != null) {
351                session.mHandler.obtainMessage(
352                        SessionHandler.MSG_REQUEST_PRINTER_UPDATE,
353                        printerId).sendToTarget();
354            }
355        }
356    };
357}
358