Fix HttpAuthHandler for synchronous requests

When HttpAuthHandler queries the WebViewClient to obtain a username and
password, we need to make sure that this is done synchronously when the request
is synchronous.

Bug: 2511043
Change-Id: I9ff2156cfb3c81edaf4a50ec7094a00a8f8ff91f
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 1c17575..6216603 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 
 import java.util.ListIterator;
 import java.util.LinkedList;
@@ -52,6 +53,14 @@
     private static final int AUTH_PROCEED = 100;
     private static final int AUTH_CANCEL = 200;
 
+    // Use to synchronize when making synchronous calls to
+    // onReceivedHttpAuthRequest(). We can't use a single Boolean object for
+    // both the lock and the state, because Boolean is immutable.
+    Object mRequestInFlightLock = new Object();
+    boolean mRequestInFlight;
+    String mUsername;
+    String mPassword;
+
     /**
      * Creates a new HTTP authentication handler with an empty
      * loader queue
@@ -70,6 +79,7 @@
         synchronized (mLoaderQueue) {
             loader = mLoaderQueue.poll();
         }
+        assert(loader.isSynchronous() == false);
 
         switch (msg.what) {
             case AUTH_PROCEED:
@@ -87,25 +97,70 @@
         processNextLoader();
     }
 
+    /**
+     * Helper method used to unblock handleAuthRequest(), which in the case of a
+     * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to
+     * call back to either proceed() or cancel().
+     *
+     * @param username The username to use for authentication
+     * @param password The password to use for authentication
+     * @return True if the request is synchronous and handleAuthRequest() has
+     * been unblocked
+     */
+    private boolean handleResponseForSynchronousRequest(String username, String password) {
+        LoadListener loader = null;
+        synchronized (mLoaderQueue) {
+            loader = mLoaderQueue.peek();
+        }
+        if (loader.isSynchronous()) {
+            mUsername = username;
+            mPassword = password;
+            return true;
+        }
+        return false;
+    }
+
+    private void signalRequestComplete() {
+        synchronized (mRequestInFlightLock) {
+            assert(mRequestInFlight);
+            mRequestInFlight = false;
+            mRequestInFlightLock.notify();
+        }
+    }
 
     /**
      * Proceed with the authorization with the given credentials
      *
+     * May be called on the UI thread, rather than the WebCore thread.
+     *
      * @param username The username to use for authentication
      * @param password The password to use for authentication
      */
     public void proceed(String username, String password) {
+        if (handleResponseForSynchronousRequest(username, password)) {
+            signalRequestComplete();
+            return;
+        }
         Message msg = obtainMessage(AUTH_PROCEED);
         msg.getData().putString("username", username);
         msg.getData().putString("password", password);
         sendMessage(msg);
+        signalRequestComplete();
     }
 
     /**
      * Cancel the authorization request
+     *
+     * May be called on the UI thread, rather than the WebCore thread.
+     *
      */
     public void cancel() {
+        if (handleResponseForSynchronousRequest(null, null)) {
+            signalRequestComplete();
+            return;
+        }
         sendMessage(obtainMessage(AUTH_CANCEL));
+        signalRequestComplete();
     }
 
     /**
@@ -132,6 +187,34 @@
      * authentication request
      */
     /* package */ void handleAuthRequest(LoadListener loader) {
+        // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If
+        // the request is synchronous, we must block here until we have a
+        // response.
+        if (loader.isSynchronous()) {
+            // If there's a request in flight, wait for it to complete. The
+            // response will queue a message on this thread.
+            waitForRequestToComplete();
+            // Make a request to the proxy for this request, jumping the queue.
+            // We use the queue so that the loader is present in
+            // useHttpAuthUsernamePassword().
+            synchronized (mLoaderQueue) {
+                mLoaderQueue.addFirst(loader);
+            }
+            processNextLoader();
+            // Wait for this request to complete.
+            waitForRequestToComplete();
+            // Pop the loader from the queue.
+            synchronized (mLoaderQueue) {
+                assert(mLoaderQueue.peek() == loader);
+                mLoaderQueue.poll();
+            }
+            // Call back.
+            loader.handleAuthResponse(mUsername, mPassword);
+            // The message queued by the response from the last asynchronous
+            // request, if present, will start the next request.
+            return;
+        }
+
         boolean processNext = false;
 
         synchronized (mLoaderQueue) {
@@ -146,6 +229,21 @@
     }
 
     /**
+     * Wait for the request in flight, if any, to complete
+     */
+    private void waitForRequestToComplete() {
+        synchronized (mRequestInFlightLock) {
+            while (mRequestInFlight) {
+                try {
+                    mRequestInFlightLock.wait();
+                } catch(InterruptedException e) {
+                    Log.e(LOGTAG, "Interrupted while waiting for request to complete");
+                }
+            }
+        }
+    }
+
+    /**
      * Process the next loader in the queue (helper method)
      */
     private void processNextLoader() {
@@ -154,6 +252,11 @@
             loader = mLoaderQueue.peek();
         }
         if (loader != null) {
+            synchronized (mRequestInFlightLock) {
+                assert(mRequestInFlight == false);
+                mRequestInFlight = true;
+            }
+
             CallbackProxy proxy = loader.getFrame().getCallbackProxy();
 
             String hostname = loader.proxyAuthenticate() ?