1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  * Copyright (C) 2016 Mopria Alliance, Inc.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bips.discovery;
19 
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.util.Log;
24 
25 import com.android.bips.BuiltInPrintService;
26 
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.concurrent.CopyOnWriteArrayList;
35 
36 /**
37  * Parent class for all printer discovery mechanisms. Subclasses must implement onStart and onStop.
38  * While started, discovery mechanisms deliver DiscoveredPrinter objects via
39  * {@link #printerFound(DiscoveredPrinter)} when they appear, and {@link #printerLost(Uri)} when
40  * they become unavailable.
41  */
42 public abstract class Discovery {
43     private static final String TAG = Discovery.class.getSimpleName();
44     private static final boolean DEBUG = false;
45 
46     private final BuiltInPrintService mPrintService;
47     private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
48     private final Map<Uri, DiscoveredPrinter> mPrinters = new HashMap<>();
49     private final Handler mHandler = new Handler(Looper.getMainLooper());
50 
51     private boolean mStarted = false;
52 
Discovery(BuiltInPrintService printService)53     Discovery(BuiltInPrintService printService) {
54         mPrintService = printService;
55     }
56 
57     /**
58      * Add a listener and begin receiving notifications from the Discovery object of any
59      * printers it finds.
60      */
start(Listener listener)61     public void start(Listener listener) {
62         mListeners.add(listener);
63 
64         // If printers are already present, signal them to the listener
65         if (!mPrinters.isEmpty()) {
66             if (!mListeners.contains(listener)) {
67                 return;
68             }
69             for (DiscoveredPrinter printer : new ArrayList<>(mPrinters.values())) {
70                 listener.onPrinterFound(printer);
71             }
72         }
73 
74         start();
75     }
76 
77     /**
78      * Remove a listener so that it no longer receives notifications of found printers.
79      * Discovery will continue for other listeners until the last one is removed.
80      */
stop(Listener listener)81     public void stop(Listener listener) {
82         mListeners.remove(listener);
83         if (mListeners.isEmpty()) {
84             stop();
85         }
86     }
87 
88     /**
89      * Return true if this object is in a started state
90      */
isStarted()91     boolean isStarted() {
92         return mStarted;
93     }
94 
95     /**
96      * Return the current print service instance
97      */
getPrintService()98     BuiltInPrintService getPrintService() {
99         return mPrintService;
100     }
101 
102     /**
103      * Return a handler to defer actions to the main (UI) thread while started. All delayed actions
104      * scheduled on this handler are cancelled at {@link #stop()} time.
105      */
getHandler()106     Handler getHandler() {
107         return mHandler;
108     }
109 
110     /**
111      * Start if not already started
112      */
start()113     private void start() {
114         if (!mStarted) {
115             mStarted = true;
116             onStart();
117         }
118     }
119 
120     /**
121      * Stop if not already stopped
122      */
stop()123     private void stop() {
124         if (mStarted) {
125             mStarted = false;
126             onStop();
127             mPrinters.clear();
128             mHandler.removeCallbacksAndMessages(null);
129         }
130     }
131 
132     /**
133      * Start searching for printers
134      */
onStart()135     abstract void onStart();
136 
137     /**
138      * Stop searching for printers, freeing any search-related resources
139      */
onStop()140     abstract void onStop();
141 
142     /**
143      * Signal that a printer appeared or possibly changed state.
144      */
printerFound(DiscoveredPrinter printer)145     void printerFound(DiscoveredPrinter printer) {
146         DiscoveredPrinter current = mPrinters.get(printer.getUri());
147         if (Objects.equals(current, printer)) {
148             if (DEBUG) Log.d(TAG, "Already have the reported printer, ignoring");
149             return;
150         }
151         mPrinters.put(printer.getUri(), printer);
152         for (Listener listener : mListeners) {
153             listener.onPrinterFound(printer);
154         }
155     }
156 
157     /**
158      * Signal that a printer is no longer visible
159      */
printerLost(Uri printerUri)160     void printerLost(Uri printerUri) {
161         DiscoveredPrinter printer = mPrinters.remove(printerUri);
162         if (printer == null) {
163             return;
164         }
165         for (Listener listener : mListeners) {
166             listener.onPrinterLost(printer);
167         }
168     }
169 
170     /** Signal loss of all printers */
allPrintersLost()171     void allPrintersLost() {
172         for (Uri uri: new ArrayList<>(mPrinters.keySet())) {
173             printerLost(uri);
174         }
175     }
176 
177     /**
178      * Return the working collection of currently-found printers
179      */
getPrinters()180     public Collection<DiscoveredPrinter> getPrinters() {
181         return mPrinters.values();
182     }
183 
184     /**
185      * Return printer matching the uri, or null if none
186      */
getPrinter(Uri uri)187     public DiscoveredPrinter getPrinter(Uri uri) {
188         return mPrinters.get(uri);
189     }
190 
191     /**
192      * Return a collection of leaf objects. By default returns a collection containing this object.
193      * Subclasses wrapping other {@link Discovery} objects should override this method.
194      */
getChildren()195     Collection<Discovery> getChildren() {
196         return Collections.singleton(this);
197     }
198 
199     /**
200      * Return a collection of saved printers. Subclasses supporting saved printers should override
201      * this method.
202      */
getSavedPrinters()203     public Collection<DiscoveredPrinter> getSavedPrinters() {
204         List<DiscoveredPrinter> printers = new ArrayList<>();
205         for (Discovery child : getChildren()) {
206             if (child != this) {
207                 printers.addAll(child.getSavedPrinters());
208             }
209         }
210         return printers;
211     }
212 
213     /**
214      * Remove a saved printer by its path. Subclasses supporting saved printers should override
215      * this method.
216      */
removeSavedPrinter(Uri printerPath)217     public void removeSavedPrinter(Uri printerPath) {
218         for (Discovery child : getChildren()) {
219             if (child != this) {
220                 child.removeSavedPrinter(printerPath);
221             }
222         }
223     }
224 
225     public interface Listener {
226         /**
227          * A new printer has been discovered, or an existing printer has been updated
228          */
onPrinterFound(DiscoveredPrinter printer)229         void onPrinterFound(DiscoveredPrinter printer);
230 
231         /**
232          * A previously-found printer is no longer discovered.
233          */
onPrinterLost(DiscoveredPrinter printer)234         void onPrinterLost(DiscoveredPrinter printer);
235     }
236 }
237