1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bips; 18 19 import android.app.PendingIntent; 20 import android.content.Intent; 21 import android.net.Uri; 22 import android.print.PrinterCapabilitiesInfo; 23 import android.print.PrinterId; 24 import android.print.PrinterInfo; 25 import android.util.Log; 26 import android.widget.Toast; 27 28 import com.android.bips.discovery.ConnectionListener; 29 import com.android.bips.discovery.DiscoveredPrinter; 30 import com.android.bips.ipp.CapabilitiesCache; 31 import com.android.bips.jni.LocalPrinterCapabilities; 32 import com.android.bips.p2p.P2pPrinterConnection; 33 import com.android.bips.p2p.P2pUtils; 34 import com.android.bips.ui.MoreOptionsActivity; 35 36 import java.net.InetAddress; 37 import java.util.Collections; 38 import java.util.UUID; 39 40 /** 41 * A session-specific printer record. Encapsulates logic for getting the latest printer 42 * capabilities as necessary. 43 */ 44 class LocalPrinter implements CapabilitiesCache.OnLocalPrinterCapabilities { 45 private static final String TAG = LocalPrinter.class.getSimpleName(); 46 private static final boolean DEBUG = false; 47 48 private final BuiltInPrintService mPrintService; 49 private final LocalDiscoverySession mSession; 50 private final PrinterId mPrinterId; 51 private long mLastSeenTime = System.currentTimeMillis(); 52 private boolean mFound = true; 53 private boolean mTracking = false; 54 private LocalPrinterCapabilities mCapabilities; 55 private DiscoveredPrinter mDiscoveredPrinter; 56 private P2pPrinterConnection mTrackingConnection; 57 LocalPrinter(BuiltInPrintService printService, LocalDiscoverySession session, DiscoveredPrinter discoveredPrinter)58 LocalPrinter(BuiltInPrintService printService, LocalDiscoverySession session, 59 DiscoveredPrinter discoveredPrinter) { 60 mPrintService = printService; 61 mSession = session; 62 mDiscoveredPrinter = discoveredPrinter; 63 mPrinterId = discoveredPrinter.getId(printService); 64 } 65 66 /** Return the address of the printer or {@code null} if not known */ getAddress()67 public InetAddress getAddress() { 68 if (mCapabilities != null) { 69 return mCapabilities.inetAddress; 70 } 71 return null; 72 } 73 74 /** Return true if this printer should be aged out */ isExpired()75 boolean isExpired() { 76 return !mFound && (System.currentTimeMillis() - mLastSeenTime) 77 > LocalDiscoverySession.PRINTER_EXPIRATION_MILLIS; 78 } 79 80 /** Return capabilities or null if not present */ getCapabilities()81 LocalPrinterCapabilities getCapabilities() { 82 return mCapabilities; 83 } 84 85 /** Create a PrinterInfo from this record or null if not possible */ createPrinterInfo(boolean knownGood)86 PrinterInfo createPrinterInfo(boolean knownGood) { 87 if (mCapabilities == null) { 88 if (P2pUtils.isP2p(mDiscoveredPrinter)) { 89 // Allow user to select a P2P to establish a connection 90 PrinterInfo.Builder builder = new PrinterInfo.Builder( 91 mPrinterId, mDiscoveredPrinter.name, 92 PrinterInfo.STATUS_IDLE) 93 .setIconResourceId(R.drawable.ic_printer) 94 .setDescription(mPrintService.getDescription(mDiscoveredPrinter)) 95 .setInfoIntent(getMoreOptionsActivityPendingIntent()); 96 return builder.build(); 97 } else if (!knownGood) { 98 // Ignore unknown LAN printers with no caps 99 return null; 100 } 101 } else if (!mCapabilities.isSupported) { 102 // Fail out if capabilities indicate not-supported. 103 return null; 104 } 105 106 // Get the most recently discovered version of this printer 107 DiscoveredPrinter printer = mPrintService.getDiscovery() 108 .getPrinter(mDiscoveredPrinter.getUri()); 109 if (printer == null) { 110 return null; 111 } 112 113 boolean idle = mFound && mCapabilities != null; 114 PrinterInfo.Builder builder = new PrinterInfo.Builder( 115 mPrinterId, printer.name, 116 idle ? PrinterInfo.STATUS_IDLE : PrinterInfo.STATUS_UNAVAILABLE) 117 .setIconResourceId(R.drawable.ic_printer) 118 .setDescription(mPrintService.getDescription(mDiscoveredPrinter)) 119 .setInfoIntent(getMoreOptionsActivityPendingIntent()); 120 121 if (mCapabilities != null) { 122 // Add capabilities if we have them 123 PrinterCapabilitiesInfo.Builder capabilitiesBuilder = 124 new PrinterCapabilitiesInfo.Builder(mPrinterId); 125 mCapabilities.buildCapabilities(mPrintService, capabilitiesBuilder); 126 builder.setCapabilities(capabilitiesBuilder.build()); 127 } 128 129 return builder.build(); 130 } 131 132 @Override onCapabilities(LocalPrinterCapabilities capabilities)133 public void onCapabilities(LocalPrinterCapabilities capabilities) { 134 if (mSession.isDestroyed() || !mSession.isKnown(mPrinterId)) { 135 return; 136 } 137 138 if (capabilities == null) { 139 if (DEBUG) Log.d(TAG, "No capabilities so removing printer " + this); 140 mSession.removePrinters(Collections.singletonList(mPrinterId)); 141 } else { 142 mCapabilities = capabilities; 143 mSession.handlePrinter(this); 144 } 145 } 146 getPrinterId()147 PrinterId getPrinterId() { 148 return mPrinterId; 149 } 150 151 /** Return true if the printer is in a "found" state according to discoveries */ isFound()152 boolean isFound() { 153 return mFound; 154 } 155 156 /** 157 * Indicate the printer was found and gather capabilities if we don't have them 158 */ found(DiscoveredPrinter printer)159 void found(DiscoveredPrinter printer) { 160 mDiscoveredPrinter = printer; 161 mLastSeenTime = System.currentTimeMillis(); 162 mFound = true; 163 164 // Check for cached capabilities 165 LocalPrinterCapabilities capabilities = mPrintService.getCapabilitiesCache() 166 .get(mDiscoveredPrinter); 167 168 if (capabilities != null) { 169 // Report current capabilities 170 onCapabilities(capabilities); 171 } else { 172 // Announce printer and fetch capabilities immediately if possible 173 mSession.handlePrinter(this); 174 if (!P2pUtils.isP2p(mDiscoveredPrinter)) { 175 mPrintService.getCapabilitiesCache().request(mDiscoveredPrinter, 176 mSession.isPriority(mPrinterId), this); 177 } else if (mTracking) { 178 startTracking(); 179 } 180 } 181 } 182 183 /** 184 * Begin tracking (getting latest capabilities) for this printer 185 */ track()186 public void track() { 187 if (DEBUG) Log.d(TAG, "track " + mDiscoveredPrinter); 188 startTracking(); 189 } 190 startTracking()191 private void startTracking() { 192 mTracking = true; 193 if (mTrackingConnection != null) { 194 return; 195 } 196 197 // For any P2P printer, obtain a connection 198 if (P2pUtils.isP2p(mDiscoveredPrinter) 199 || P2pUtils.isOnConnectedInterface(mPrintService, mDiscoveredPrinter)) { 200 ConnectionListener listener = new ConnectionListener() { 201 @Override 202 public void onConnectionComplete(DiscoveredPrinter printer) { 203 if (DEBUG) Log.d(TAG, "connection complete " + printer); 204 if (printer == null) { 205 mTrackingConnection = null; 206 } 207 } 208 209 @Override 210 public void onConnectionDelayed(boolean delayed) { 211 if (DEBUG) Log.d(TAG, "connection delayed=" + delayed); 212 if (delayed) { 213 Toast.makeText(mPrintService, R.string.connect_hint_text, 214 Toast.LENGTH_LONG).show(); 215 } 216 } 217 }; 218 mTrackingConnection = new P2pPrinterConnection(mPrintService, 219 mDiscoveredPrinter, listener); 220 } 221 } 222 223 /** 224 * Stop tracking this printer 225 */ stopTracking()226 void stopTracking() { 227 if (mTrackingConnection != null) { 228 mTrackingConnection.close(); 229 mTrackingConnection = null; 230 } 231 mTracking = false; 232 } 233 234 /** 235 * Mark this printer as not found (will eventually expire) 236 */ notFound()237 void notFound() { 238 mFound = false; 239 mLastSeenTime = System.currentTimeMillis(); 240 } 241 242 /** Return the UUID for this printer if it is known */ getUuid()243 public Uri getUuid() { 244 return mDiscoveredPrinter.uuid; 245 } 246 247 @Override toString()248 public String toString() { 249 return mDiscoveredPrinter.toString(); 250 } 251 252 /** 253 * Returns a pending intent to the more options activity with the given printer info as an extra 254 * @return Pending Intent 255 */ getMoreOptionsActivityPendingIntent()256 public PendingIntent getMoreOptionsActivityPendingIntent() { 257 return PendingIntent.getActivity( 258 mPrintService, 259 mPrinterId.hashCode(), 260 new Intent(mPrintService, MoreOptionsActivity.class) 261 .setIdentifier(UUID.randomUUID().toString()) 262 .putExtra(MoreOptionsActivity.EXTRA_PRINTER_ID, mPrinterId), 263 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE 264 ); 265 } 266 } 267