Use network-specific HTTP proxy settings in captive portal login app.

The HTTP proxy system properties are set based on the proxy configured
for the network the captive portal login app is operating on.  These
system properties are subsequently read by the WebView used to perform
the sign-in.
This is a short-term fix using reflection.  This allows users with
cellular providers that use proxies to sign into WiFi captive portals.
The long-term fix could involve:
1. Sending out a network-specific proxy config changed broadcast, and
2. Modifying the ActivityThread to set proxy system properties for
   Networks selected with setProcessDefaultNetwork(), and
3. Modifying WebView to read from the proxy system properties when
   PROXY_CHANGE_ACTION broadcasts are received.

bug:17679789
Change-Id: I3ada0470ad085df1a4452b4a655ac35f310d2241
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index b3a6e88..7a262de 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -17,16 +17,24 @@
 package com.android.captiveportallogin;
 
 import android.app.Activity;
+import android.app.LoadedApk;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.util.ArrayMap;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -42,8 +50,11 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.lang.InterruptedException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 
 public class CaptivePortalLoginActivity extends Activity {
+    private static final String TAG = "CaptivePortalLogin";
     private static final String DEFAULT_SERVER = "clients3.google.com";
     private static final int SOCKET_TIMEOUT_MS = 10000;
 
@@ -72,14 +83,34 @@
             done(true);
         }
 
-        setContentView(R.layout.activity_captive_portal_login);
-
-        getActionBar().setDisplayShowHomeEnabled(false);
-
         mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT));
         final Network network = new Network(mNetId);
         ConnectivityManager.setProcessDefaultNetwork(network);
 
+        // Set HTTP proxy system properties to those of the selected Network.
+        final LinkProperties lp = ConnectivityManager.from(this).getLinkProperties(network);
+        if (lp != null) {
+            final ProxyInfo proxyInfo = lp.getHttpProxy();
+            String host = "";
+            String port = "";
+            String exclList = "";
+            Uri pacFileUrl = Uri.EMPTY;
+            if (proxyInfo != null) {
+                host = proxyInfo.getHost();
+                port = Integer.toString(proxyInfo.getPort());
+                exclList = proxyInfo.getExclusionListAsString();
+                pacFileUrl = proxyInfo.getPacFileUrl();
+            }
+            Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+            Log.v(TAG, "Set proxy system properties to " + proxyInfo);
+        }
+
+        // Proxy system properties must be initialized before setContentView is called because
+        // setContentView initializes the WebView logic which in turn reads the system properties.
+        setContentView(R.layout.activity_captive_portal_login);
+
+        getActionBar().setDisplayShowHomeEnabled(false);
+
         // Exit app if Network disappears.
         final NetworkCapabilities networkCapabilities =
                 ConnectivityManager.from(this).getNetworkCapabilities(network);
@@ -99,12 +130,39 @@
         }
         ConnectivityManager.from(this).registerNetworkCallback(builder.build(), mNetworkCallback);
 
-        WebView myWebView = (WebView) findViewById(R.id.webview);
+        final WebView myWebView = (WebView) findViewById(R.id.webview);
+        myWebView.clearCache(true);
         WebSettings webSettings = myWebView.getSettings();
         webSettings.setJavaScriptEnabled(true);
         myWebView.setWebViewClient(new MyWebViewClient());
         myWebView.setWebChromeClient(new MyWebChromeClient());
-        myWebView.loadUrl(mURL.toString());
+        // Start initial page load so WebView finishes loading proxy settings.
+        // Actual load of mUrl is initiated by MyWebViewClient.
+        myWebView.loadData("", "text/html", null);
+    }
+
+    // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
+    private void setWebViewProxy() {
+        LoadedApk loadedApk = getApplication().mLoadedApk;
+        try {
+            Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
+            receiversField.setAccessible(true);
+            ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
+            for (Object receiverMap : receivers.values()) {
+                for (Object rec : ((ArrayMap) receiverMap).keySet()) {
+                    Class clazz = rec.getClass();
+                    if (clazz.getName().contains("ProxyChangeListener")) {
+                        Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
+                                Intent.class);
+                        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+                        onReceiveMethod.invoke(rec, getApplicationContext(), intent);
+                        Log.v(TAG, "Prompting WebView proxy reload.");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while setting WebView proxy: " + e);
+        }
     }
 
     private void done(boolean use_network) {
@@ -176,13 +234,25 @@
     }
 
     private class MyWebViewClient extends WebViewClient {
+        private boolean firstPageLoad = true;
+
         @Override
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            if (firstPageLoad) return;
             testForCaptivePortal();
         }
 
         @Override
         public void onPageFinished(WebView view, String url) {
+            if (firstPageLoad) {
+                firstPageLoad = false;
+                // Now that WebView has loaded at least one page we know it has read in the proxy
+                // settings.  Now prompt the WebView read the Network-specific proxy settings.
+                setWebViewProxy();
+                // Load the real page.
+                view.loadUrl(mURL.toString());
+                return;
+            }
             testForCaptivePortal();
         }
     }