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.print;
18
19import android.content.Context;
20import android.content.pm.ParceledListSlice;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.os.RemoteException;
25import android.util.ArrayMap;
26import android.util.Log;
27
28import java.lang.ref.WeakReference;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.LinkedHashMap;
32import java.util.List;
33
34/**
35 * @hide
36 */
37public final class PrinterDiscoverySession {
38
39    private static final String LOG_TAG ="PrinterDiscoverySession";
40
41    private static final int MSG_PRINTERS_ADDED = 1;
42    private static final int MSG_PRINTERS_REMOVED = 2;
43
44    private final LinkedHashMap<PrinterId, PrinterInfo> mPrinters =
45            new LinkedHashMap<PrinterId, PrinterInfo>();
46
47    private final IPrintManager mPrintManager;
48
49    private final int mUserId;
50
51    private final Handler mHandler;
52
53    private IPrinterDiscoveryObserver mObserver;
54
55    private OnPrintersChangeListener mListener;
56
57    private boolean mIsPrinterDiscoveryStarted;
58
59    public static interface OnPrintersChangeListener {
60        public void onPrintersChanged();
61    }
62
63    PrinterDiscoverySession(IPrintManager printManager, Context context, int userId) {
64        mPrintManager = printManager;
65        mUserId = userId;
66        mHandler = new SessionHandler(context.getMainLooper());
67        mObserver = new PrinterDiscoveryObserver(this);
68        try {
69            mPrintManager.createPrinterDiscoverySession(mObserver, mUserId);
70        } catch (RemoteException re) {
71            Log.e(LOG_TAG, "Error creating printer discovery session", re);
72        }
73    }
74
75    public final void startPrinterDisovery(List<PrinterId> priorityList) {
76        if (isDestroyed()) {
77            Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed");
78            return;
79        }
80        if (!mIsPrinterDiscoveryStarted) {
81            mIsPrinterDiscoveryStarted = true;
82            try {
83                mPrintManager.startPrinterDiscovery(mObserver, priorityList, mUserId);
84            } catch (RemoteException re) {
85                Log.e(LOG_TAG, "Error starting printer discovery", re);
86            }
87        }
88    }
89
90    public final void stopPrinterDiscovery() {
91        if (isDestroyed()) {
92            Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed");
93            return;
94        }
95        if (mIsPrinterDiscoveryStarted) {
96            mIsPrinterDiscoveryStarted = false;
97            try {
98                mPrintManager.stopPrinterDiscovery(mObserver, mUserId);
99            } catch (RemoteException re) {
100                Log.e(LOG_TAG, "Error stopping printer discovery", re);
101            }
102        }
103    }
104
105    public final void startPrinterStateTracking(PrinterId printerId) {
106        if (isDestroyed()) {
107            Log.w(LOG_TAG, "Ignoring start printer state tracking - session destroyed");
108            return;
109        }
110        try {
111            mPrintManager.startPrinterStateTracking(printerId, mUserId);
112        } catch (RemoteException re) {
113            Log.e(LOG_TAG, "Error starting printer state tracking", re);
114        }
115    }
116
117    public final void stopPrinterStateTracking(PrinterId printerId) {
118        if (isDestroyed()) {
119            Log.w(LOG_TAG, "Ignoring stop printer state tracking - session destroyed");
120            return;
121        }
122        try {
123            mPrintManager.stopPrinterStateTracking(printerId, mUserId);
124        } catch (RemoteException re) {
125            Log.e(LOG_TAG, "Error stoping printer state tracking", re);
126        }
127    }
128
129    public final void validatePrinters(List<PrinterId> printerIds) {
130        if (isDestroyed()) {
131            Log.w(LOG_TAG, "Ignoring validate printers - session destroyed");
132            return;
133        }
134        try {
135            mPrintManager.validatePrinters(printerIds, mUserId);
136        } catch (RemoteException re) {
137            Log.e(LOG_TAG, "Error validating printers", re);
138        }
139    }
140
141    public final void destroy() {
142        if (isDestroyed()) {
143            Log.w(LOG_TAG, "Ignoring destroy - session destroyed");
144        }
145        destroyNoCheck();
146    }
147
148    public final List<PrinterInfo> getPrinters() {
149        if (isDestroyed()) {
150            Log.w(LOG_TAG, "Ignoring get printers - session destroyed");
151            return Collections.emptyList();
152        }
153        return new ArrayList<PrinterInfo>(mPrinters.values());
154    }
155
156    public final boolean isDestroyed() {
157        throwIfNotCalledOnMainThread();
158        return isDestroyedNoCheck();
159    }
160
161    public final boolean isPrinterDiscoveryStarted() {
162        throwIfNotCalledOnMainThread();
163        return mIsPrinterDiscoveryStarted;
164    }
165
166    public final void setOnPrintersChangeListener(OnPrintersChangeListener listener) {
167        throwIfNotCalledOnMainThread();
168        mListener = listener;
169    }
170
171    @Override
172    protected final void finalize() throws Throwable {
173        if (!isDestroyedNoCheck()) {
174            Log.e(LOG_TAG, "Destroying leaked printer discovery session");
175            destroyNoCheck();
176        }
177        super.finalize();
178    }
179
180    private boolean isDestroyedNoCheck() {
181        return (mObserver == null);
182    }
183
184    private void destroyNoCheck() {
185        stopPrinterDiscovery();
186        try {
187            mPrintManager.destroyPrinterDiscoverySession(mObserver, mUserId);
188        } catch (RemoteException re) {
189            Log.e(LOG_TAG, "Error destroying printer discovery session", re);
190        } finally {
191            mObserver = null;
192            mPrinters.clear();
193        }
194    }
195
196    private void handlePrintersAdded(List<PrinterInfo> addedPrinters) {
197        if (isDestroyed()) {
198            return;
199        }
200
201        // No old printers - do not bother keeping their position.
202        if (mPrinters.isEmpty()) {
203            final int printerCount = addedPrinters.size();
204            for (int i = 0; i < printerCount; i++) {
205                PrinterInfo printer = addedPrinters.get(i);
206                mPrinters.put(printer.getId(), printer);
207            }
208            notifyOnPrintersChanged();
209            return;
210        }
211
212        // Add the printers to a map.
213        ArrayMap<PrinterId, PrinterInfo> addedPrintersMap =
214                new ArrayMap<PrinterId, PrinterInfo>();
215        final int printerCount = addedPrinters.size();
216        for (int i = 0; i < printerCount; i++) {
217            PrinterInfo printer = addedPrinters.get(i);
218            addedPrintersMap.put(printer.getId(), printer);
219        }
220
221        // Update printers we already have.
222        for (PrinterId oldPrinterId : mPrinters.keySet()) {
223            PrinterInfo updatedPrinter = addedPrintersMap.remove(oldPrinterId);
224            if (updatedPrinter != null) {
225                mPrinters.put(oldPrinterId, updatedPrinter);
226            }
227        }
228
229        // Add the new printers, i.e. what is left.
230        mPrinters.putAll(addedPrintersMap);
231
232        // Announce the change.
233        notifyOnPrintersChanged();
234    }
235
236    private void handlePrintersRemoved(List<PrinterId> printerIds) {
237        if (isDestroyed()) {
238            return;
239        }
240        boolean printersChanged = false;
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                printersChanged = true;
246            }
247        }
248        if (printersChanged) {
249            notifyOnPrintersChanged();
250        }
251    }
252
253    private void notifyOnPrintersChanged() {
254        if (mListener != null) {
255            mListener.onPrintersChanged();
256        }
257    }
258
259    private static void throwIfNotCalledOnMainThread() {
260        if (!Looper.getMainLooper().isCurrentThread()) {
261            throw new IllegalAccessError("must be called from the main thread");
262        }
263    }
264
265    private final class SessionHandler extends Handler {
266
267        public SessionHandler(Looper looper) {
268            super(looper, null, false);
269        }
270
271        @Override
272        @SuppressWarnings("unchecked")
273        public void handleMessage(Message message) {
274            switch (message.what) {
275                case MSG_PRINTERS_ADDED: {
276                    List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
277                    handlePrintersAdded(printers);
278                } break;
279
280                case MSG_PRINTERS_REMOVED: {
281                    List<PrinterId> printerIds = (List<PrinterId>) message.obj;
282                    handlePrintersRemoved(printerIds);
283                } break;
284            }
285        }
286    }
287
288    private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
289
290        private final WeakReference<PrinterDiscoverySession> mWeakSession;
291
292        public PrinterDiscoveryObserver(PrinterDiscoverySession session) {
293            mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
294        }
295
296        @Override
297        @SuppressWarnings("rawtypes")
298        public void onPrintersAdded(ParceledListSlice printers) {
299            PrinterDiscoverySession session = mWeakSession.get();
300            if (session != null) {
301                session.mHandler.obtainMessage(MSG_PRINTERS_ADDED,
302                        printers.getList()).sendToTarget();
303            }
304        }
305
306        @Override
307        @SuppressWarnings("rawtypes")
308        public void onPrintersRemoved(ParceledListSlice printerIds) {
309            PrinterDiscoverySession session = mWeakSession.get();
310            if (session != null) {
311                session.mHandler.obtainMessage(MSG_PRINTERS_REMOVED,
312                        printerIds.getList()).sendToTarget();
313            }
314        }
315    }
316}
317