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.text.TextUtils; 22 import android.util.Log; 23 24 import com.android.bips.BuiltInPrintService; 25 import com.android.bips.ipp.CapabilitiesCache; 26 import com.android.bips.jni.LocalPrinterCapabilities; 27 import com.android.bips.util.WifiMonitor; 28 29 import java.util.ArrayList; 30 import java.util.Collection; 31 import java.util.LinkedHashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 /** 36 * Manage a list of printers manually added by the user. 37 */ 38 public class ManualDiscovery extends SavedDiscovery { 39 private static final String TAG = ManualDiscovery.class.getSimpleName(); 40 private static final boolean DEBUG = false; 41 42 // Likely paths at which a print service may be found 43 private static final Uri[] IPP_URIS = {Uri.parse("ipp://host:631/ipp/print"), 44 Uri.parse("ipp://host:80/ipp/print"), Uri.parse("ipp://host:631/ipp/printer"), 45 Uri.parse("ipp://host:631/ipp"), Uri.parse("ipp://host:631/"), 46 Uri.parse("ipps://host:631/ipp/print"), Uri.parse("ipps://host:443/ipp/print"), 47 Uri.parse("ipps://host:10443/ipp/print")}; 48 49 private WifiMonitor mWifiMonitor; 50 private CapabilitiesCache mCapabilitiesCache; 51 private List<CapabilitiesFinder> mAddRequests = new ArrayList<>(); 52 ManualDiscovery(BuiltInPrintService printService)53 public ManualDiscovery(BuiltInPrintService printService) { 54 super(printService); 55 mCapabilitiesCache = getPrintService().getCapabilitiesCache(); 56 } 57 58 @Override onStart()59 void onStart() { 60 if (DEBUG) Log.d(TAG, "onStart"); 61 62 // Upon any network change scan for all manually added printers 63 mWifiMonitor = new WifiMonitor(getPrintService(), isConnected -> { 64 if (isConnected) { 65 for (DiscoveredPrinter printer : getSavedPrinters()) { 66 mCapabilitiesCache.request(printer, false, capabilities -> { 67 if (capabilities != null) { 68 printerFound(printer); 69 } 70 }); 71 } 72 } else { 73 allPrintersLost(); 74 } 75 }); 76 } 77 78 @Override onStop()79 void onStop() { 80 if (DEBUG) Log.d(TAG, "onStop"); 81 mWifiMonitor.close(); 82 allPrintersLost(); 83 } 84 85 /** 86 * Asynchronously attempt to add a new manual printer, calling back with success if 87 * printer capabilities were discovered. 88 * 89 * The supplied URI must include a hostname and may also include a scheme (either ipp:// or 90 * ipps://), a port (such as :443), and/or a path (like /ipp/print). If any parts are missing, 91 * typical known values are substituted and searched until success is found, or all are 92 * tried unsuccessfully. 93 * 94 * @param printerUri URI to search 95 */ addManualPrinter(Uri printerUri, PrinterAddCallback callback)96 public void addManualPrinter(Uri printerUri, PrinterAddCallback callback) { 97 if (DEBUG) Log.d(TAG, "addManualPrinter " + printerUri); 98 99 int givenPort = printerUri.getPort(); 100 String givenPath = printerUri.getPath(); 101 String hostname = printerUri.getHost(); 102 String givenScheme = printerUri.getScheme(); 103 104 // Use LinkedHashSet to eliminate duplicates but maintain order 105 Set<Uri> uris = new LinkedHashSet<>(); 106 for (Uri uri : IPP_URIS) { 107 String scheme = uri.getScheme(); 108 if (!TextUtils.isEmpty(givenScheme) && !scheme.equals(givenScheme)) { 109 // If scheme was supplied and doesn't match this uri template, skip 110 continue; 111 } 112 String authority = hostname + ":" + (givenPort == -1 ? uri.getPort() : givenPort); 113 String path = TextUtils.isEmpty(givenPath) ? uri.getPath() : givenPath; 114 Uri targetUri = uri.buildUpon().scheme(scheme).encodedAuthority(authority).path(path) 115 .build(); 116 uris.add(targetUri); 117 } 118 119 mAddRequests.add(new CapabilitiesFinder(uris, callback)); 120 } 121 122 /** 123 * Cancel a prior {@link #addManualPrinter(Uri, PrinterAddCallback)} attempt having the same 124 * callback 125 */ cancelAddManualPrinter(PrinterAddCallback callback)126 public void cancelAddManualPrinter(PrinterAddCallback callback) { 127 for (CapabilitiesFinder finder : mAddRequests) { 128 if (finder.mFinalCallback == callback) { 129 mAddRequests.remove(finder); 130 finder.cancel(); 131 return; 132 } 133 } 134 } 135 136 /** Used to convey response to {@link #addManualPrinter} */ 137 public interface PrinterAddCallback { 138 /** 139 * The requested manual printer was found. 140 * 141 * @param printer information about the discovered printer 142 * @param supported true if the printer is supported (and was therefore added), or false 143 * if the printer was found but is not supported (and was therefore not 144 * added) 145 */ onFound(DiscoveredPrinter printer, boolean supported)146 void onFound(DiscoveredPrinter printer, boolean supported); 147 148 /** 149 * The requested manual printer was not found. 150 */ onNotFound()151 void onNotFound(); 152 } 153 154 /** 155 * Search common printer paths for a successful response 156 */ 157 private class CapabilitiesFinder { 158 private final PrinterAddCallback mFinalCallback; 159 private final List<CapabilitiesCache.OnLocalPrinterCapabilities> mRequests = 160 new ArrayList<>(); 161 162 /** 163 * Constructs a new finder 164 * 165 * @param uris Locations to check for IPP endpoints 166 * @param callback Callback to issue when the first successful response arrives, or 167 * when all responses have failed. 168 */ CapabilitiesFinder(Collection<Uri> uris, PrinterAddCallback callback)169 CapabilitiesFinder(Collection<Uri> uris, PrinterAddCallback callback) { 170 mFinalCallback = callback; 171 for (Uri uri : uris) { 172 CapabilitiesCache.OnLocalPrinterCapabilities capabilitiesCallback = 173 new CapabilitiesCache.OnLocalPrinterCapabilities() { 174 @Override 175 public void onCapabilities(LocalPrinterCapabilities capabilities) { 176 mRequests.remove(this); 177 handleCapabilities(uri, capabilities); 178 } 179 }; 180 mRequests.add(capabilitiesCallback); 181 182 // Force a clean attempt from scratch 183 mCapabilitiesCache.remove(uri); 184 mCapabilitiesCache.request(new DiscoveredPrinter(null, "", uri, null), 185 true, capabilitiesCallback); 186 } 187 } 188 189 /** Capabilities have arrived (or not) for the printer at a given path */ handleCapabilities(Uri printerPath, LocalPrinterCapabilities capabilities)190 void handleCapabilities(Uri printerPath, LocalPrinterCapabilities capabilities) { 191 if (DEBUG) Log.d(TAG, "request " + printerPath + " cap=" + capabilities); 192 193 if (capabilities == null) { 194 if (mRequests.isEmpty()) { 195 mAddRequests.remove(this); 196 mFinalCallback.onNotFound(); 197 } 198 return; 199 } 200 201 // Success, so cancel all other requests 202 for (CapabilitiesCache.OnLocalPrinterCapabilities request : mRequests) { 203 mCapabilitiesCache.cancel(request); 204 } 205 mRequests.clear(); 206 207 // Deliver a successful response 208 Uri uuid = TextUtils.isEmpty(capabilities.uuid) ? null : Uri.parse(capabilities.uuid); 209 String name = TextUtils.isEmpty(capabilities.name) ? printerPath.getHost() 210 : capabilities.name; 211 212 DiscoveredPrinter resolvedPrinter = new DiscoveredPrinter(uuid, name, printerPath, 213 capabilities.location); 214 215 // Only add supported printers 216 if (capabilities.isSupported) { 217 if (addSavedPrinter(resolvedPrinter)) { 218 printerFound(resolvedPrinter); 219 } 220 } 221 mAddRequests.remove(this); 222 mFinalCallback.onFound(resolvedPrinter, capabilities.isSupported); 223 } 224 225 /** Stop all in-progress capability requests that are in progress */ cancel()226 public void cancel() { 227 for (CapabilitiesCache.OnLocalPrinterCapabilities callback : mRequests) { 228 mCapabilitiesCache.cancel(callback); 229 } 230 mRequests.clear(); 231 } 232 } 233 } 234