improve printer discovery
am: e604a9fafd

Change-Id: I05cac2710636f1cc61349885920ae642d0759601
diff --git a/jni/include/lib_wprint.h b/jni/include/lib_wprint.h
index 8e07937..3703a3d 100644
--- a/jni/include/lib_wprint.h
+++ b/jni/include/lib_wprint.h
@@ -206,6 +206,8 @@
     const char *uri_path;
     const char *uri_scheme;
     int port_num;
+    /* Timeout per retry in milliseconds */
+    long timeout;
 } wprint_connect_info_t;
 
 /*
diff --git a/jni/ipphelper/ipphelper.c b/jni/ipphelper/ipphelper.c
index a0e6243..2b16ed3 100644
--- a/jni/ipphelper/ipphelper.c
+++ b/jni/ipphelper/ipphelper.c
@@ -970,13 +970,23 @@
                     idx, SupportedMediaSizes[idx].PWGName);
         }
     }
-    LOGD("");
 
-    if ((attrptr = ippFindAttribute(response, "printer-name", IPP_TAG_NAME)) != NULL) {
-        LOGD("printer-name: %s", ippGetString(attrptr, 0, NULL));
+    if ((attrptr = ippFindAttribute(response, "printer-dns-sd-name", IPP_TAG_NAME)) != NULL) {
         strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
     }
 
+    if (!capabilities->name[0]) {
+        if ((attrptr = ippFindAttribute(response, "printer-info", IPP_TAG_TEXT)) != NULL) {
+            strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
+        }
+    }
+
+    if (!capabilities->name[0]) {
+        if ((attrptr = ippFindAttribute(response, "printer-name", IPP_TAG_TEXT)) != NULL) {
+            strlcpy(capabilities->name, ippGetString(attrptr, 0, NULL), sizeof(capabilities->name));
+        }
+    }
+
     if ((attrptr = ippFindAttribute(response, "printer-make-and-model", IPP_TAG_TEXT)) != NULL) {
         strlcpy(capabilities->make, ippGetString(attrptr, 0, NULL), sizeof(capabilities->make));
     }
@@ -1417,7 +1427,7 @@
 
     curl_http = httpConnect(connect_info->printer_addr, ippPortNumber);
 
-    httpSetTimeout(curl_http, DEFAULT_IPP_TIMEOUT, NULL, 0);
+    httpSetTimeout(curl_http, (double)connect_info->timeout / 1000, NULL, 0);
     httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, uriLength, connect_info->uri_scheme, NULL,
             connect_info->printer_addr, ippPortNumber, uri_path);
 
diff --git a/jni/ipphelper/ipphelper.h b/jni/ipphelper/ipphelper.h
index 928eba9..15bd26a 100644
--- a/jni/ipphelper/ipphelper.h
+++ b/jni/ipphelper/ipphelper.h
@@ -28,7 +28,8 @@
 #include "ipp.h"
 #include "ifc_wprint.h"
 
-#define DEFAULT_IPP_TIMEOUT 15.0
+/* Default timeout for most operations */
+#define DEFAULT_IPP_TIMEOUT (15 * 1000)
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/jni/ipphelper/ippstatus_capabilities.c b/jni/ipphelper/ippstatus_capabilities.c
index f37896f..2c34861 100644
--- a/jni/ipphelper/ippstatus_capabilities.c
+++ b/jni/ipphelper/ippstatus_capabilities.c
@@ -34,6 +34,8 @@
 static const char *pattrs[] = {
         "ipp-versions-supported",
         "printer-make-and-model",
+        "printer-info",
+        "printer-dns-sd-name",
         "printer-name",
         "printer-location",
         "printer-uuid",
diff --git a/jni/lib/lib_wprint.c b/jni/lib/lib_wprint.c
index d524c8a..d905d86 100644
--- a/jni/lib/lib_wprint.c
+++ b/jni/lib/lib_wprint.c
@@ -741,6 +741,7 @@
                 connect_info.uri_path = jq->printer_uri;
                 connect_info.port_num = jq->port_num;
                 connect_info.uri_scheme = IPP_PREFIX;
+                connect_info.timeout = DEFAULT_IPP_TIMEOUT;
                 jq->status_ifc->init(jq->status_ifc, &connect_info);
             }
             // wait for the printer to be idle
diff --git a/jni/lib/wprintJNI.c b/jni/lib/wprintJNI.c
index b2fa7cf..1dbddb8 100644
--- a/jni/lib/wprintJNI.c
+++ b/jni/lib/wprintJNI.c
@@ -1150,7 +1150,7 @@
  */
 JNIEXPORT jint JNICALL Java_com_android_bips_ipp_Backend_nativeGetCapabilities(
         JNIEnv *env, jobject obj, jstring address, jint port, jstring httpResource,
-        jstring uriScheme, jobject printerCaps) {
+        jstring uriScheme, jlong timeout, jobject printerCaps) {
     jint result;
     printer_capabilities_t caps;
     wprint_connect_info_t connect_info;
@@ -1159,6 +1159,7 @@
     connect_info.uri_path = copyToNewString(env, httpResource);
     connect_info.uri_scheme = copyToNewString(env, uriScheme);
     connect_info.port_num = port;
+    connect_info.timeout = timeout;
 
     LOGI("nativeGetCapabilities for %s JNIenv is %p", connect_info.printer_addr, env);
 
diff --git a/src/com/android/bips/LocalDiscoverySession.java b/src/com/android/bips/LocalDiscoverySession.java
index 13d12ef..b1625be 100644
--- a/src/com/android/bips/LocalDiscoverySession.java
+++ b/src/com/android/bips/LocalDiscoverySession.java
@@ -188,6 +188,13 @@
             mKnownGood.add(0, localPrinter.getPrinterId());
         }
 
+        for (PrinterInfo knownInfo : getPrinters()) {
+            if (knownInfo.getId().equals(info.getId()) && (info.getCapabilities() == null)) {
+                if (DEBUG) Log.d(TAG, "Ignore update with no caps " + localPrinter);
+                return;
+            }
+        }
+
         if (DEBUG) {
             Log.d(TAG, "handlePrinter: reporting " + localPrinter +
                     " caps=" + (info.getCapabilities() != null) + " status=" + info.getStatus());
@@ -200,7 +207,7 @@
      * Return true if the {@link PrinterId} corresponds to a high-priority printer
      */
     boolean isPriority(PrinterId printerId) {
-        return mPriorityIds.contains(printerId) || mTrackingIds.contains(printerId);
+        return mTrackingIds.contains(printerId);
     }
 
     /**
diff --git a/src/com/android/bips/LocalPrintJob.java b/src/com/android/bips/LocalPrintJob.java
index 36d9ee6..924d036 100644
--- a/src/com/android/bips/LocalPrintJob.java
+++ b/src/com/android/bips/LocalPrintJob.java
@@ -147,7 +147,7 @@
         return mPrintJob;
     }
 
-    private void handleCapabilities(LocalPrinterCapabilities capabilities) {
+    private void handleCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities) {
         if (DEBUG) Log.d(TAG, "Capabilities for " + mPath + " are " + capabilities);
         if (mState != STATE_DISCOVERY) return;
 
diff --git a/src/com/android/bips/LocalPrinter.java b/src/com/android/bips/LocalPrinter.java
index 5155856..9c95632 100644
--- a/src/com/android/bips/LocalPrinter.java
+++ b/src/com/android/bips/LocalPrinter.java
@@ -71,10 +71,15 @@
             return null;
         }
 
-        String description = mDiscoveredPrinter.getDescription(mPrintService);
+        // Get the most recently discovered version of this printer
+        DiscoveredPrinter printer = mPrintService.getDiscovery()
+                .getPrinter(mDiscoveredPrinter.getUri());
+        if (printer == null) return null;
+
+        String description = printer.getDescription(mPrintService);
         boolean idle = mFound && mCapabilities != null;
         PrinterInfo.Builder builder = new PrinterInfo.Builder(
-                mPrinterId, mDiscoveredPrinter.name,
+                mPrinterId, printer.name,
                 idle ? PrinterInfo.STATUS_IDLE : PrinterInfo.STATUS_UNAVAILABLE)
                 .setDescription(description);
 
@@ -90,7 +95,7 @@
     }
 
     @Override
-    public void onCapabilities(LocalPrinterCapabilities capabilities) {
+    public void onCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities) {
         if (mSession.isDestroyed() || !mSession.isKnown(mPrinterId)) return;
 
         if (capabilities == null) {
@@ -132,7 +137,7 @@
 
         if (capabilities != null) {
             // Report current capabilities
-            onCapabilities(capabilities);
+            onCapabilities(mDiscoveredPrinter, capabilities);
         } else {
             // Announce printer and fetch capabilities
             mSession.handlePrinter(this);
diff --git a/src/com/android/bips/discovery/DiscoveredPrinter.java b/src/com/android/bips/discovery/DiscoveredPrinter.java
index 1f46d81..1b383ba 100644
--- a/src/com/android/bips/discovery/DiscoveredPrinter.java
+++ b/src/com/android/bips/discovery/DiscoveredPrinter.java
@@ -137,6 +137,18 @@
         writer.endObject();
     }
 
+    /** Combine the best (longest) elements of this record and another into a merged record */
+    DiscoveredPrinter bestOf(DiscoveredPrinter other) {
+        return new DiscoveredPrinter(uuid, longest(name, other.name), path,
+                longest(location, other.location));
+    }
+
+    private static String longest(String a, String b) {
+        if (a == null) return b;
+        if (b == null) return a;
+        return a.length() > b.length() ? a : b;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof DiscoveredPrinter)) return false;
diff --git a/src/com/android/bips/discovery/Discovery.java b/src/com/android/bips/discovery/Discovery.java
index 2d48d22..8422750 100644
--- a/src/com/android/bips/discovery/Discovery.java
+++ b/src/com/android/bips/discovery/Discovery.java
@@ -158,6 +158,13 @@
         return mPrinters.values();
     }
 
+    /**
+     * Return printer matching the uri, or null if none
+     */
+    public DiscoveredPrinter getPrinter(Uri uri) {
+        return mPrinters.get(uri);
+    }
+
     public interface Listener {
         void onPrinterFound(DiscoveredPrinter printer);
 
diff --git a/src/com/android/bips/discovery/ManualDiscovery.java b/src/com/android/bips/discovery/ManualDiscovery.java
index 0bce5ae..c884697 100644
--- a/src/com/android/bips/discovery/ManualDiscovery.java
+++ b/src/com/android/bips/discovery/ManualDiscovery.java
@@ -48,10 +48,9 @@
     private static final String CACHE_FILE = TAG + ".json";
 
     // Likely paths at which a print service may be found
-    private static final String[] IPP_PATHS = {"ipp/printer", "ipp/print", "ipp", ""};
-
-    private static final int DEFAULT_IPP_PORT = 631;
-    private static final String DEFAULT_IPP_SCHEME = "ipp";
+    private static final Uri[] IPP_URIS = { Uri.parse("ipp://path:631/ipp/print"),
+            Uri.parse("ipp://path:80/ipp/print"), Uri.parse("ipp://path:631/ipp/printer"),
+            Uri.parse("ipp://path:631/ipp"), Uri.parse("ipp://path:631/")};
 
     private final List<DiscoveredPrinter> mManualPrinters = new ArrayList<>();
 
@@ -86,11 +85,7 @@
      */
     public void addManualPrinter(String hostname, PrinterAddCallback callback) {
         if (DEBUG) Log.d(TAG, "addManualPrinter " + hostname);
-
-        // Repair supplied hostname as much as possible
-        Uri base = Uri.parse(DEFAULT_IPP_SCHEME + "://" + hostname + ":" + DEFAULT_IPP_PORT);
-
-        new CapabilitiesFinder(base, callback).startNext();
+        new CapabilitiesFinder(hostname, callback);
     }
 
     private void addManualPrinter(DiscoveredPrinter printer) {
@@ -198,62 +193,56 @@
      * Search common printer paths for a successful response
      */
     private class CapabilitiesFinder implements CapabilitiesCache.OnLocalPrinterCapabilities {
-        private final LinkedList<String> mPaths = new LinkedList<>();
+        private final LinkedList<Uri> mUris = new LinkedList<>();
         private final PrinterAddCallback mFinalCallback;
-        private final Uri mBase;
+        private final String mHostname;
 
         /**
-         * Constructs a new callback handler
+         * Constructs a new finder
          *
-         * @param base     Base URI for print service to find
+         * @param hostname Hostname to crawl for IPP endpoints
          * @param callback Callback to issue when the first successful response arrives, or
          *                 when all responses have failed.
          */
-        CapabilitiesFinder(Uri base, PrinterAddCallback callback) {
-            mPaths.addAll(Arrays.asList(IPP_PATHS));
+        CapabilitiesFinder(String hostname, PrinterAddCallback callback) {
             mFinalCallback = callback;
-            mBase = base;
-        }
+            mHostname = hostname;
 
-        /** Move on to the next path or report failure if none remain */
-        void startNext() {
-            if (mPaths.isEmpty()) {
-                mFinalCallback.onNotFound();
-            } else {
-                Uri uriToTry = mBase.buildUpon().encodedPath(mPaths.pop()).build();
-                DiscoveredPrinter printer = new DiscoveredPrinter(null, "unknown", uriToTry, null);
-                getPrintService().getCapabilitiesCache().request(printer, false, this);
+            for (Uri uri : IPP_URIS) {
+                uri = uri.buildUpon().encodedAuthority(mHostname + ":" + uri.getPort()).build();
+                mUris.add(uri);
+                DiscoveredPrinter printer = new DiscoveredPrinter(null, "unknown", uri, null);
+                getPrintService().getCapabilitiesCache().request(printer, true, this);
             }
         }
 
         @Override
-        public void onCapabilities(LocalPrinterCapabilities capabilities) {
+        public void onCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities) {
             if (DEBUG) Log.d(TAG, "onCapabilities: " + capabilities);
-
+            mUris.remove(printer.getUri());
             if (capabilities == null) {
-                startNext();
+                if (mUris.isEmpty()) {
+                    mFinalCallback.onNotFound();
+                }
                 return;
             }
 
-            // Deliver a successful response
-            Uri path = Uri.parse(capabilities.path);
-            if (path.getPort() == -1) {
-                // Fix missing port
-                path = path.buildUpon().encodedAuthority(path.getAuthority() + ":" +
-                        DEFAULT_IPP_PORT).build();
-            }
-            Uri uuid = TextUtils.isEmpty(capabilities.uuid) ? null : Uri.parse(capabilities.uuid);
-            String name = TextUtils.isEmpty(capabilities.name) ? path.getHost() : capabilities.name;
+            // Success, so cancel all other requests
+            getPrintService().getCapabilitiesCache().cancel(this);
 
-            DiscoveredPrinter printer = new DiscoveredPrinter(uuid, name, path,
+            // Deliver a successful response
+            Uri uuid = TextUtils.isEmpty(capabilities.uuid) ? null : Uri.parse(capabilities.uuid);
+            String name = TextUtils.isEmpty(capabilities.name) ? printer.getUri().getHost() : capabilities.name;
+
+            DiscoveredPrinter resolvedPrinter = new DiscoveredPrinter(uuid, name, printer.getUri(),
                     capabilities.location);
 
             // Only add supported printers
             if (capabilities.isSupported) {
-                addManualPrinter(printer);
+                addManualPrinter(resolvedPrinter);
             }
 
-            mFinalCallback.onFound(printer, capabilities.isSupported);
+            mFinalCallback.onFound(resolvedPrinter, capabilities.isSupported);
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/bips/discovery/MultiDiscovery.java b/src/com/android/bips/discovery/MultiDiscovery.java
index ed4873e..29863fb 100644
--- a/src/com/android/bips/discovery/MultiDiscovery.java
+++ b/src/com/android/bips/discovery/MultiDiscovery.java
@@ -44,6 +44,11 @@
         mChildListener = new Listener() {
             @Override
             public void onPrinterFound(DiscoveredPrinter printer) {
+                DiscoveredPrinter oldPrinter = getPrinter(printer.getUri());
+                if (oldPrinter != null) {
+                    printer = printer.bestOf(oldPrinter);
+                }
+
                 MultiDiscovery.this.printerFound(printer);
             }
 
diff --git a/src/com/android/bips/ipp/Backend.java b/src/com/android/bips/ipp/Backend.java
index 0c1fe8b..ef0c722 100644
--- a/src/com/android/bips/ipp/Backend.java
+++ b/src/com/android/bips/ipp/Backend.java
@@ -89,11 +89,11 @@
     }
 
     /** Asynchronously get printer capabilities, returning results or null to a callback */
-    public void getCapabilities(Uri uri,
+    public AsyncTask<?, ?, ?> getCapabilities(Uri uri, long timeout,
             final Consumer<LocalPrinterCapabilities> capabilitiesConsumer) {
         if (DEBUG) Log.d(TAG, "getCapabilities()");
 
-        new GetCapabilitiesTask(this, uri) {
+        return new GetCapabilitiesTask(this, uri, timeout) {
             @Override
             protected void onPostExecute(LocalPrinterCapabilities result) {
                 capabilitiesConsumer.accept(result);
@@ -264,11 +264,12 @@
      * @param port port to use (e.g. 631)
      * @param httpResource path of print resource on host (e.g. "/ipp/print")
      * @param uriScheme scheme (e.g. "ipp")
+     * @param timeout milliseconds to wait before giving up on request
      * @param capabilities target object to be filled with printer capabilities, if successful
      * @return {@link BackendConstants#STATUS_OK} or an error code.
      */
     native int nativeGetCapabilities(String address, int port, String httpResource,
-            String uriScheme, LocalPrinterCapabilities capabilities);
+            String uriScheme, long timeout, LocalPrinterCapabilities capabilities);
 
     /**
      * Determine initial parameters to be used for jobs
diff --git a/src/com/android/bips/ipp/CapabilitiesCache.java b/src/com/android/bips/ipp/CapabilitiesCache.java
index 0109f0e..14c5b83 100644
--- a/src/com/android/bips/ipp/CapabilitiesCache.java
+++ b/src/com/android/bips/ipp/CapabilitiesCache.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LruCache;
@@ -27,8 +28,10 @@
 import com.android.bips.jni.LocalPrinterCapabilities;
 import com.android.bips.util.WifiMonitor;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -49,6 +52,12 @@
     // Maximum number of printers expected on a single network
     private static final int CACHE_SIZE = 100;
 
+    // Maximum time per retry before giving up on first pass
+    private static final int FIRST_PASS_TIMEOUT = 500;
+
+    // Maximum time per retry before giving up on second pass. Must differ from FIRST_PASS_TIMEOUT.
+    private static final int SECOND_PASS_TIMEOUT = 8000;
+
     private final Map<Uri, Request> mRequests = new HashMap<>();
     private final Set<Uri> mToEvict = new HashSet<>();
     private final int mMaxConcurrent;
@@ -92,7 +101,7 @@
 
     /** Callback for receiving capabilities */
     public interface OnLocalPrinterCapabilities {
-        void onCapabilities(LocalPrinterCapabilities capabilities);
+        void onCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities);
     }
 
     /**
@@ -112,18 +121,23 @@
         Uri printerPath = printer.path;
         LocalPrinterCapabilities capabilities = get(printer.getUri());
         if (capabilities != null && capabilities.nativeData != null) {
-            onLocalPrinterCapabilities.onCapabilities(capabilities);
+            onLocalPrinterCapabilities.onCapabilities(printer, capabilities);
             return;
         }
 
         Request request = mRequests.get(printerUri);
         if (request == null) {
-            request = new Request(printer);
+            if (highPriority) {
+                // Go straight to the long-timeout request
+                request = new Request(printer, SECOND_PASS_TIMEOUT);
+            } else {
+                request = new Request(printer, FIRST_PASS_TIMEOUT);
+            }
             mRequests.put(printerUri, request);
         } else if (!request.printer.path.equals(printerPath)) {
             Log.w(TAG, "Capabilities request for printer " + printer +
                     " overlaps with different path " + request.printer.path);
-            onLocalPrinterCapabilities.onCapabilities(null);
+            onLocalPrinterCapabilities.onCapabilities(printer, null);
             return;
         }
 
@@ -136,13 +150,31 @@
         startNextRequest();
     }
 
+    /**
+     * Cancel any outstanding attempts to get capabilities on this callback
+     */
+    public void cancel(OnLocalPrinterCapabilities onLocalPrinterCapabilities) {
+        List<Uri> toDrop = new ArrayList<>();
+        for (Map.Entry<Uri, Request> entry : mRequests.entrySet()) {
+            Request request = entry.getValue();
+            request.callbacks.remove(onLocalPrinterCapabilities);
+            if (request.callbacks.isEmpty()) {
+                // There is no further interest in this request so cancel it
+                toDrop.add(entry.getKey());
+                if (request.query != null) {
+                    request.query.cancel(true);
+                }
+            }
+        }
+        toDrop.forEach(mRequests::remove);
+    }
+
     /** Look for next query and launch it */
     private void startNextRequest() {
         final Request request = getNextRequest();
         if (request == null) return;
 
-        request.querying = true;
-        mBackend.getCapabilities(request.printer.path, capabilities -> {
+        request.query = mBackend.getCapabilities(request.printer.path, request.timeout, capabilities -> {
             DiscoveredPrinter printer = request.printer;
             if (DEBUG) Log.d(TAG, "Capabilities for " + printer + " cap=" + capabilities);
 
@@ -162,7 +194,16 @@
             }
 
             if (capabilities == null) {
-                remove(printer.getUri());
+                if (request.timeout == FIRST_PASS_TIMEOUT) {
+                    // Printer did not respond quickly, try again in the slow lane
+                    request.timeout = SECOND_PASS_TIMEOUT;
+                    request.query = null;
+                    mRequests.put(printer.getUri(), request);
+                    startNextRequest();
+                    return;
+                } else {
+                    remove(printer.getUri());
+                }
             } else {
                 Uri key = printer.getUri();
                 if (printer.uuid == null) {
@@ -177,7 +218,7 @@
             }
 
             for (OnLocalPrinterCapabilities callback : request.callbacks) {
-                callback.onCapabilities(capabilities);
+                callback.onCapabilities(printer, capabilities);
             }
             startNextRequest();
         });
@@ -188,10 +229,11 @@
         Request found = null;
         int total = 0;
         for (Request request : mRequests.values()) {
-            if (request.querying) {
+            if (request.query != null) {
                 total++;
-            } else if (found == null || (!found.highPriority && request.highPriority)) {
-                // First outstanding, or higher highPriority request
+            } else if (found == null || (!found.highPriority && request.highPriority) ||
+                    (found.highPriority == request.highPriority && request.timeout < found.timeout)) {
+                // First valid or higher priority request
                 found = request;
             }
         }
@@ -202,14 +244,16 @@
     }
 
     /** Holds an outstanding capabilities request */
-    private class Request {
+    public class Request {
         final DiscoveredPrinter printer;
         final Set<OnLocalPrinterCapabilities> callbacks = new HashSet<>();
-        boolean querying = false;
-        boolean highPriority = true;
+        AsyncTask<?, ?, ?> query;
+        boolean highPriority = false;
+        long timeout;
 
-        Request(DiscoveredPrinter printer) {
+        Request(DiscoveredPrinter printer, long timeout) {
             this.printer = printer;
+            this.timeout = timeout;
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/bips/ipp/GetCapabilitiesTask.java b/src/com/android/bips/ipp/GetCapabilitiesTask.java
index 6d09bb8..5554ba6 100644
--- a/src/com/android/bips/ipp/GetCapabilitiesTask.java
+++ b/src/com/android/bips/ipp/GetCapabilitiesTask.java
@@ -38,21 +38,20 @@
     /** Lock to ensure we don't issue multiple simultaneous capability requests */
     private static final Lock sJniLock = new ReentrantLock();
 
-    /** Amount of time before giving up on the "online" check for printer */
-    private static final int ONLINE_TIMEOUT_MILLIS = 6000;
-
     private final Backend mBackend;
     private final Uri mUri;
+    private final long mTimeout;
 
-    GetCapabilitiesTask(Backend backend, Uri uri) {
+    GetCapabilitiesTask(Backend backend, Uri uri, long timeout) {
         mUri = uri;
         mBackend = backend;
+        mTimeout = timeout;
     }
 
-    private static boolean isDeviceOnline(Uri uri) {
+    private boolean isDeviceOnline(Uri uri) {
         try (Socket socket = new Socket()) {
             InetSocketAddress a = new InetSocketAddress(uri.getHost(), uri.getPort());
-            socket.connect(a, ONLINE_TIMEOUT_MILLIS);
+            socket.connect(a, (int) mTimeout);
             return true;
         } catch (IOException e) {
             return false;
@@ -70,15 +69,16 @@
                     " (" + (System.currentTimeMillis() - start) + "ms)");
         }
 
-        if (!online) return null;
+        if (!online || isCancelled()) return null;
 
         // Do not permit more than a single call to this API or crashes may result
         sJniLock.lock();
         int status = -1;
         start = System.currentTimeMillis();
         try {
+            if (isCancelled()) return null;
             status = mBackend.nativeGetCapabilities(Backend.getIp(mUri.getHost()),
-                    mUri.getPort(), mUri.getPath(), mUri.getScheme(), printerCaps);
+                    mUri.getPort(), mUri.getPath(), mUri.getScheme(), mTimeout, printerCaps);
         } finally {
             sJniLock.unlock();
         }