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; 19 20 import android.print.PrintManager; 21 import android.print.PrinterId; 22 import android.print.PrinterInfo; 23 import android.printservice.PrintServiceInfo; 24 import android.printservice.PrinterDiscoverySession; 25 import android.printservice.recommendation.RecommendationInfo; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.Log; 29 30 import com.android.bips.discovery.DiscoveredPrinter; 31 import com.android.bips.discovery.Discovery; 32 33 import java.net.InetAddress; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Set; 41 42 class LocalDiscoverySession extends PrinterDiscoverySession implements Discovery.Listener, 43 PrintManager.PrintServiceRecommendationsChangeListener, 44 PrintManager.PrintServicesChangeListener { 45 private static final String TAG = LocalDiscoverySession.class.getSimpleName(); 46 private static final boolean DEBUG = false; 47 48 // Printers are removed after not being seen for this long 49 static final int PRINTER_EXPIRATION_MILLIS = 3000; 50 51 private final BuiltInPrintService mPrintService; 52 private final Map<PrinterId, LocalPrinter> mPrinters = new HashMap<>(); 53 private final Set<PrinterId> mTrackingIds = new HashSet<>(); 54 private final LocalDiscoverySessionInfo mInfo; 55 private DelayedAction mExpirePrinters; 56 private PrintManager mPrintManager; 57 58 /** Package names of all currently enabled print services beside this one */ 59 private ArraySet<String> mEnabledServices = new ArraySet<>(); 60 61 /** 62 * Address of printers that can be handled by print services, ordered by package name of the 63 * print service. The print service might not be enabled. For that, look at 64 * {@link #mEnabledServices}. 65 * 66 * <p>This print service only shows a printer if another print service does not show it. 67 */ 68 private final ArrayMap<InetAddress, ArrayList<String>> mPrintersOfOtherService = 69 new ArrayMap<>(); 70 LocalDiscoverySession(BuiltInPrintService service)71 LocalDiscoverySession(BuiltInPrintService service) { 72 mPrintService = service; 73 mPrintManager = mPrintService.getSystemService(PrintManager.class); 74 mInfo = new LocalDiscoverySessionInfo(service); 75 } 76 77 @Override onStartPrinterDiscovery(List<PrinterId> priorityList)78 public void onStartPrinterDiscovery(List<PrinterId> priorityList) { 79 if (DEBUG) Log.d(TAG, "onStartPrinterDiscovery() " + priorityList); 80 81 // Mark all known printers as "not found". They may return shortly or may expire 82 for (LocalPrinter printer : mPrinters.values()) { 83 printer.notFound(); 84 } 85 monitorExpiredPrinters(); 86 87 mPrintService.getDiscovery().start(this); 88 89 mPrintManager.addPrintServicesChangeListener(this, null); 90 onPrintServicesChanged(); 91 92 mPrintManager.addPrintServiceRecommendationsChangeListener(this, null); 93 onPrintServiceRecommendationsChanged(); 94 } 95 96 @Override onStopPrinterDiscovery()97 public void onStopPrinterDiscovery() { 98 if (DEBUG) Log.d(TAG, "onStopPrinterDiscovery()"); 99 mPrintService.getDiscovery().stop(this); 100 101 PrintManager printManager = mPrintService.getSystemService(PrintManager.class); 102 printManager.removePrintServicesChangeListener(this); 103 printManager.removePrintServiceRecommendationsChangeListener(this); 104 105 if (mExpirePrinters != null) { 106 mExpirePrinters.cancel(); 107 mExpirePrinters = null; 108 } 109 } 110 111 @Override onValidatePrinters(List<PrinterId> printerIds)112 public void onValidatePrinters(List<PrinterId> printerIds) { 113 if (DEBUG) Log.d(TAG, "onValidatePrinters() " + printerIds); 114 } 115 116 @Override onStartPrinterStateTracking(final PrinterId printerId)117 public void onStartPrinterStateTracking(final PrinterId printerId) { 118 if (DEBUG) Log.d(TAG, "onStartPrinterStateTracking() " + printerId); 119 LocalPrinter localPrinter = mPrinters.get(printerId); 120 mTrackingIds.add(printerId); 121 122 // We cannot track the printer yet; wait until it is discovered 123 if (localPrinter == null || !localPrinter.isFound()) { 124 return; 125 } 126 localPrinter.track(); 127 } 128 129 @Override onStopPrinterStateTracking(PrinterId printerId)130 public void onStopPrinterStateTracking(PrinterId printerId) { 131 if (DEBUG) Log.d(TAG, "onStopPrinterStateTracking() " + printerId.getLocalId()); 132 LocalPrinter localPrinter = mPrinters.get(printerId); 133 if (localPrinter != null) { 134 localPrinter.stopTracking(); 135 } 136 mTrackingIds.remove(printerId); 137 } 138 139 @Override onDestroy()140 public void onDestroy() { 141 if (DEBUG) Log.d(TAG, "onDestroy"); 142 mInfo.save(); 143 } 144 145 /** 146 * A printer was found during discovery 147 */ 148 @Override onPrinterFound(DiscoveredPrinter discoveredPrinter)149 public void onPrinterFound(DiscoveredPrinter discoveredPrinter) { 150 if (DEBUG) Log.d(TAG, "onPrinterFound() " + discoveredPrinter); 151 if (isDestroyed()) { 152 Log.w(TAG, "Destroyed; ignoring"); 153 return; 154 } 155 156 PrinterId printerId = discoveredPrinter.getId(mPrintService); 157 LocalPrinter localPrinter = mPrinters.computeIfAbsent(printerId, 158 id -> new LocalPrinter(mPrintService, this, discoveredPrinter)); 159 160 localPrinter.found(discoveredPrinter); 161 if (mTrackingIds.contains(printerId)) { 162 localPrinter.track(); 163 } 164 } 165 166 /** 167 * A printer was lost during discovery 168 */ 169 @Override onPrinterLost(DiscoveredPrinter lostPrinter)170 public void onPrinterLost(DiscoveredPrinter lostPrinter) { 171 if (DEBUG) Log.d(TAG, "onPrinterLost() " + lostPrinter); 172 173 mPrintService.getCapabilitiesCache().remove(lostPrinter.path); 174 175 PrinterId printerId = lostPrinter.getId(mPrintService); 176 LocalPrinter localPrinter = mPrinters.get(printerId); 177 if (localPrinter == null) { 178 return; 179 } 180 181 localPrinter.notFound(); 182 handlePrinter(localPrinter); 183 monitorExpiredPrinters(); 184 } 185 monitorExpiredPrinters()186 private void monitorExpiredPrinters() { 187 if (mExpirePrinters == null && !mPrinters.isEmpty()) { 188 mExpirePrinters = mPrintService.delay(PRINTER_EXPIRATION_MILLIS, () -> { 189 mExpirePrinters = null; 190 boolean allFound = true; 191 List<PrinterId> idsToRemove = new ArrayList<>(); 192 193 for (LocalPrinter localPrinter : mPrinters.values()) { 194 if (localPrinter.isExpired()) { 195 if (DEBUG) Log.d(TAG, "Expiring " + localPrinter); 196 idsToRemove.add(localPrinter.getPrinterId()); 197 } 198 if (!localPrinter.isFound()) { 199 allFound = false; 200 } 201 } 202 for (PrinterId id : idsToRemove) { 203 mPrinters.remove(id); 204 } 205 removePrinters(idsToRemove); 206 if (!allFound) { 207 monitorExpiredPrinters(); 208 } 209 }); 210 } 211 } 212 213 /** A complete printer record is available */ handlePrinter(LocalPrinter localPrinter)214 void handlePrinter(LocalPrinter localPrinter) { 215 if (DEBUG) Log.d(TAG, "handlePrinter record " + localPrinter); 216 217 boolean knownGood = mInfo.isKnownGood(localPrinter.getPrinterId()); 218 PrinterInfo info = localPrinter.createPrinterInfo(knownGood); 219 if (info == null) { 220 return; 221 } 222 223 if (info.getStatus() == PrinterInfo.STATUS_IDLE && localPrinter.getUuid() != null) { 224 // Mark UUID-based printers with IDLE status as known-good 225 mInfo.setKnownGood(localPrinter.getPrinterId()); 226 } 227 228 for (PrinterInfo knownInfo : getPrinters()) { 229 if (knownInfo.getId().equals(info.getId()) && (info.getCapabilities() == null)) { 230 if (DEBUG) Log.d(TAG, "Ignore update with no caps " + localPrinter); 231 return; 232 } 233 } 234 235 if (DEBUG) { 236 Log.d(TAG, "handlePrinter: reporting " + localPrinter 237 + " caps=" + (info.getCapabilities() != null) + " status=" + info.getStatus() 238 + " summary=" + info.getDescription()); 239 } 240 241 if (!isHandledByOtherService(localPrinter)) { 242 addPrinters(Collections.singletonList(info)); 243 } 244 } 245 246 /** 247 * Return true if the {@link PrinterId} corresponds to a high-priority printer 248 */ isPriority(PrinterId printerId)249 boolean isPriority(PrinterId printerId) { 250 return mTrackingIds.contains(printerId); 251 } 252 253 /** 254 * Return true if the {@link PrinterId} corresponds to a known printer 255 */ isKnown(PrinterId printerId)256 boolean isKnown(PrinterId printerId) { 257 return mPrinters.containsKey(printerId); 258 } 259 260 /** 261 * Is this printer handled by another print service and should be suppressed? 262 * 263 * @param printer The printer that might need to be suppressed 264 * 265 * @return {@code true} iff the printer should be suppressed 266 */ isHandledByOtherService(LocalPrinter printer)267 private boolean isHandledByOtherService(LocalPrinter printer) { 268 InetAddress address = printer.getAddress(); 269 if (address == null) { 270 return false; 271 } 272 273 ArrayList<String> printerServices = mPrintersOfOtherService.get(printer.getAddress()); 274 275 if (printerServices != null) { 276 int numServices = printerServices.size(); 277 for (int i = 0; i < numServices; i++) { 278 if (mEnabledServices.contains(printerServices.get(i))) { 279 return true; 280 } 281 } 282 } 283 284 return false; 285 } 286 287 /** 288 * If the system's print service state changed some printer might be newly suppressed or not 289 * suppressed anymore. 290 */ onPrintServicesStateUpdated()291 private void onPrintServicesStateUpdated() { 292 ArrayList<PrinterInfo> printersToAdd = new ArrayList<>(); 293 ArrayList<PrinterId> printersToRemove = new ArrayList<>(); 294 for (LocalPrinter printer : mPrinters.values()) { 295 boolean knownGood = mInfo.isKnownGood(printer.getPrinterId()); 296 PrinterInfo info = printer.createPrinterInfo(knownGood); 297 298 if (printer.getCapabilities() != null && printer.isFound() 299 && !isHandledByOtherService(printer) && info != null) { 300 printersToAdd.add(info); 301 } else { 302 printersToRemove.add(printer.getPrinterId()); 303 } 304 } 305 306 removePrinters(printersToRemove); 307 addPrinters(printersToAdd); 308 } 309 310 @Override onPrintServiceRecommendationsChanged()311 public void onPrintServiceRecommendationsChanged() { 312 mPrintersOfOtherService.clear(); 313 314 List<RecommendationInfo> infos = mPrintManager.getPrintServiceRecommendations(); 315 316 int numInfos = infos.size(); 317 for (int i = 0; i < numInfos; i++) { 318 RecommendationInfo info = infos.get(i); 319 String packageName = info.getPackageName().toString(); 320 321 if (!packageName.equals(mPrintService.getPackageName())) { 322 for (InetAddress address : info.getDiscoveredPrinters()) { 323 ArrayList<String> services = mPrintersOfOtherService.get(address); 324 325 if (services == null) { 326 services = new ArrayList<>(1); 327 mPrintersOfOtherService.put(address, services); 328 } 329 330 services.add(packageName); 331 } 332 } 333 } 334 335 onPrintServicesStateUpdated(); 336 } 337 338 @Override onPrintServicesChanged()339 public void onPrintServicesChanged() { 340 mEnabledServices.clear(); 341 342 List<PrintServiceInfo> infos = mPrintManager.getPrintServices( 343 PrintManager.ENABLED_SERVICES); 344 345 int numInfos = infos.size(); 346 for (int i = 0; i < numInfos; i++) { 347 PrintServiceInfo info = infos.get(i); 348 String packageName = info.getComponentName().getPackageName(); 349 350 if (!packageName.equals(mPrintService.getPackageName())) { 351 mEnabledServices.add(packageName); 352 } 353 } 354 355 onPrintServicesStateUpdated(); 356 } 357 } 358