/* * Copyright (C) 2016 The Android Open Source Project * Copyright (C) 2016 Mopria Alliance, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bips.discovery; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.util.Log; import com.android.bips.BuiltInPrintService; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; /** * Parent class for all printer discovery mechanisms. Subclasses must implement onStart and onStop. * While started, discovery mechanisms deliver DiscoveredPrinter objects via * {@link #printerFound(DiscoveredPrinter)} when they appear, and {@link #printerLost(Uri)} when * they become unavailable. */ public abstract class Discovery { private static final String TAG = Discovery.class.getSimpleName(); private static final boolean DEBUG = false; private final BuiltInPrintService mPrintService; private final List mListeners = new CopyOnWriteArrayList<>(); private final Map mPrinters = new HashMap<>(); private final Handler mHandler = new Handler(Looper.getMainLooper()); private boolean mStarted = false; Discovery(BuiltInPrintService printService) { mPrintService = printService; } /** * Add a listener and begin receiving notifications from the Discovery object of any * printers it finds. */ public void start(Listener listener) { mListeners.add(listener); // If printers are already present, signal them to the listener if (!mPrinters.isEmpty()) { if (!mListeners.contains(listener)) { return; } for (DiscoveredPrinter printer : new ArrayList<>(mPrinters.values())) { listener.onPrinterFound(printer); } } start(); } /** * Remove a listener so that it no longer receives notifications of found printers. * Discovery will continue for other listeners until the last one is removed. */ public void stop(Listener listener) { mListeners.remove(listener); if (mListeners.isEmpty()) { stop(); } } /** * Return true if this object is in a started state */ boolean isStarted() { return mStarted; } /** * Return the current print service instance */ BuiltInPrintService getPrintService() { return mPrintService; } /** * Return a handler to defer actions to the main (UI) thread while started. All delayed actions * scheduled on this handler are cancelled at {@link #stop()} time. */ Handler getHandler() { return mHandler; } /** * Start if not already started */ private void start() { if (!mStarted) { mStarted = true; onStart(); } } /** * Stop if not already stopped */ private void stop() { if (mStarted) { mStarted = false; onStop(); mPrinters.clear(); mHandler.removeCallbacksAndMessages(null); } } /** * Start searching for printers */ abstract void onStart(); /** * Stop searching for printers, freeing any search-related resources */ abstract void onStop(); /** * Signal that a printer appeared or possibly changed state. */ void printerFound(DiscoveredPrinter printer) { DiscoveredPrinter current = mPrinters.get(printer.getUri()); if (Objects.equals(current, printer)) { if (DEBUG) Log.d(TAG, "Already have the reported printer, ignoring"); return; } mPrinters.put(printer.getUri(), printer); for (Listener listener : mListeners) { listener.onPrinterFound(printer); } } /** * Signal that a printer is no longer visible */ void printerLost(Uri printerUri) { DiscoveredPrinter printer = mPrinters.remove(printerUri); if (printer == null) { return; } for (Listener listener : mListeners) { listener.onPrinterLost(printer); } } /** Signal loss of all printers */ void allPrintersLost() { for (Uri uri: new ArrayList<>(mPrinters.keySet())) { printerLost(uri); } } /** * Return the working collection of currently-found printers */ public Collection getPrinters() { return mPrinters.values(); } /** * Return printer matching the uri, or null if none */ public DiscoveredPrinter getPrinter(Uri uri) { return mPrinters.get(uri); } /** * Return a collection of leaf objects. By default returns a collection containing this object. * Subclasses wrapping other {@link Discovery} objects should override this method. */ Collection getChildren() { return Collections.singleton(this); } /** * Return a collection of saved printers. Subclasses supporting saved printers should override * this method. */ public Collection getSavedPrinters() { List printers = new ArrayList<>(); for (Discovery child : getChildren()) { if (child != this) { printers.addAll(child.getSavedPrinters()); } } return printers; } /** * Remove a saved printer by its path. Subclasses supporting saved printers should override * this method. */ public void removeSavedPrinter(Uri printerPath) { for (Discovery child : getChildren()) { if (child != this) { child.removeSavedPrinter(printerPath); } } } public interface Listener { /** * A new printer has been discovered, or an existing printer has been updated */ void onPrinterFound(DiscoveredPrinter printer); /** * A previously-found printer is no longer discovered. */ void onPrinterLost(DiscoveredPrinter printer); } }