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