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