CaptivePortalLoginActivity.java revision 88eb0fa8eec7da1b7a3bd39f9d9844909911bc64
1/* 2 * Copyright (C) 2014 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 17package com.android.captiveportallogin; 18 19import android.app.Activity; 20import android.app.LoadedApk; 21import android.content.Context; 22import android.content.Intent; 23import android.graphics.Bitmap; 24import android.net.ConnectivityManager; 25import android.net.ConnectivityManager.NetworkCallback; 26import android.net.LinkProperties; 27import android.net.Network; 28import android.net.NetworkCapabilities; 29import android.net.NetworkRequest; 30import android.net.Proxy; 31import android.net.ProxyInfo; 32import android.net.Uri; 33import android.os.Bundle; 34import android.provider.Settings; 35import android.provider.Settings.Global; 36import android.util.ArrayMap; 37import android.util.Log; 38import android.view.Menu; 39import android.view.MenuItem; 40import android.view.View; 41import android.view.Window; 42import android.webkit.WebChromeClient; 43import android.webkit.WebSettings; 44import android.webkit.WebView; 45import android.webkit.WebViewClient; 46import android.widget.ProgressBar; 47 48import java.io.IOException; 49import java.net.HttpURLConnection; 50import java.net.MalformedURLException; 51import java.net.URL; 52import java.lang.InterruptedException; 53import java.lang.reflect.Field; 54import java.lang.reflect.Method; 55 56public class CaptivePortalLoginActivity extends Activity { 57 private static final String TAG = "CaptivePortalLogin"; 58 private static final String DEFAULT_SERVER = "clients3.google.com"; 59 private static final int SOCKET_TIMEOUT_MS = 10000; 60 61 // Keep this in sync with NetworkMonitor. 62 // Intent broadcast to ConnectivityService indicating sign-in is complete. 63 // Extras: 64 // EXTRA_TEXT = netId 65 // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. 66 private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = 67 "android.net.netmon.captive_portal_logged_in"; 68 private static final String LOGGED_IN_RESULT = "result"; 69 70 private URL mURL; 71 private int mNetId; 72 private NetworkCallback mNetworkCallback; 73 74 @Override 75 protected void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 78 String server = Settings.Global.getString(getContentResolver(), "captive_portal_server"); 79 if (server == null) server = DEFAULT_SERVER; 80 try { 81 mURL = new URL("http://" + server + "/generate_204"); 82 } catch (MalformedURLException e) { 83 done(true); 84 } 85 86 mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT)); 87 final Network network = new Network(mNetId); 88 ConnectivityManager.setProcessDefaultNetwork(network); 89 90 // Set HTTP proxy system properties to those of the selected Network. 91 final LinkProperties lp = ConnectivityManager.from(this).getLinkProperties(network); 92 if (lp != null) { 93 final ProxyInfo proxyInfo = lp.getHttpProxy(); 94 String host = ""; 95 String port = ""; 96 String exclList = ""; 97 Uri pacFileUrl = Uri.EMPTY; 98 if (proxyInfo != null) { 99 host = proxyInfo.getHost(); 100 port = Integer.toString(proxyInfo.getPort()); 101 exclList = proxyInfo.getExclusionListAsString(); 102 pacFileUrl = proxyInfo.getPacFileUrl(); 103 } 104 Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); 105 Log.v(TAG, "Set proxy system properties to " + proxyInfo); 106 } 107 108 // Proxy system properties must be initialized before setContentView is called because 109 // setContentView initializes the WebView logic which in turn reads the system properties. 110 setContentView(R.layout.activity_captive_portal_login); 111 112 getActionBar().setDisplayShowHomeEnabled(false); 113 114 // Exit app if Network disappears. 115 final NetworkCapabilities networkCapabilities = 116 ConnectivityManager.from(this).getNetworkCapabilities(network); 117 if (networkCapabilities == null) { 118 finish(); 119 return; 120 } 121 mNetworkCallback = new NetworkCallback() { 122 @Override 123 public void onLost(Network lostNetwork) { 124 if (network.equals(lostNetwork)) done(false); 125 } 126 }; 127 final NetworkRequest.Builder builder = new NetworkRequest.Builder(); 128 for (int transportType : networkCapabilities.getTransportTypes()) { 129 builder.addTransportType(transportType); 130 } 131 ConnectivityManager.from(this).registerNetworkCallback(builder.build(), mNetworkCallback); 132 133 final WebView myWebView = (WebView) findViewById(R.id.webview); 134 myWebView.clearCache(true); 135 WebSettings webSettings = myWebView.getSettings(); 136 webSettings.setJavaScriptEnabled(true); 137 myWebView.setWebViewClient(new MyWebViewClient()); 138 myWebView.setWebChromeClient(new MyWebChromeClient()); 139 // Start initial page load so WebView finishes loading proxy settings. 140 // Actual load of mUrl is initiated by MyWebViewClient. 141 myWebView.loadData("", "text/html", null); 142 } 143 144 // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties. 145 private void setWebViewProxy() { 146 LoadedApk loadedApk = getApplication().mLoadedApk; 147 try { 148 Field receiversField = LoadedApk.class.getDeclaredField("mReceivers"); 149 receiversField.setAccessible(true); 150 ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk); 151 for (Object receiverMap : receivers.values()) { 152 for (Object rec : ((ArrayMap) receiverMap).keySet()) { 153 Class clazz = rec.getClass(); 154 if (clazz.getName().contains("ProxyChangeListener")) { 155 Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, 156 Intent.class); 157 Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); 158 onReceiveMethod.invoke(rec, getApplicationContext(), intent); 159 Log.v(TAG, "Prompting WebView proxy reload."); 160 } 161 } 162 } 163 } catch (Exception e) { 164 Log.e(TAG, "Exception while setting WebView proxy: " + e); 165 } 166 } 167 168 private void done(boolean use_network) { 169 ConnectivityManager.from(this).unregisterNetworkCallback(mNetworkCallback); 170 Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN); 171 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId)); 172 intent.putExtra(LOGGED_IN_RESULT, use_network ? "1" : "0"); 173 sendBroadcast(intent); 174 finish(); 175 } 176 177 @Override 178 public boolean onCreateOptionsMenu(Menu menu) { 179 getMenuInflater().inflate(R.menu.captive_portal_login, menu); 180 return true; 181 } 182 183 @Override 184 public void onBackPressed() { 185 WebView myWebView = (WebView) findViewById(R.id.webview); 186 if (myWebView.canGoBack()) { 187 myWebView.goBack(); 188 } else { 189 super.onBackPressed(); 190 } 191 } 192 193 @Override 194 public boolean onOptionsItemSelected(MenuItem item) { 195 int id = item.getItemId(); 196 if (id == R.id.action_use_network) { 197 done(true); 198 return true; 199 } 200 if (id == R.id.action_do_not_use_network) { 201 done(false); 202 return true; 203 } 204 return super.onOptionsItemSelected(item); 205 } 206 207 private void testForCaptivePortal() { 208 new Thread(new Runnable() { 209 public void run() { 210 // Give time for captive portal to open. 211 try { 212 Thread.sleep(1000); 213 } catch (InterruptedException e) { 214 } 215 HttpURLConnection urlConnection = null; 216 int httpResponseCode = 500; 217 try { 218 urlConnection = (HttpURLConnection) mURL.openConnection(); 219 urlConnection.setInstanceFollowRedirects(false); 220 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 221 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 222 urlConnection.setUseCaches(false); 223 urlConnection.getInputStream(); 224 httpResponseCode = urlConnection.getResponseCode(); 225 } catch (IOException e) { 226 } finally { 227 if (urlConnection != null) urlConnection.disconnect(); 228 } 229 if (httpResponseCode == 204) { 230 done(true); 231 } 232 } 233 }).start(); 234 } 235 236 private class MyWebViewClient extends WebViewClient { 237 private boolean firstPageLoad = true; 238 239 @Override 240 public void onPageStarted(WebView view, String url, Bitmap favicon) { 241 if (firstPageLoad) return; 242 testForCaptivePortal(); 243 } 244 245 @Override 246 public void onPageFinished(WebView view, String url) { 247 if (firstPageLoad) { 248 firstPageLoad = false; 249 // Now that WebView has loaded at least one page we know it has read in the proxy 250 // settings. Now prompt the WebView read the Network-specific proxy settings. 251 setWebViewProxy(); 252 // Load the real page. 253 view.loadUrl(mURL.toString()); 254 return; 255 } 256 testForCaptivePortal(); 257 } 258 } 259 260 private class MyWebChromeClient extends WebChromeClient { 261 @Override 262 public void onProgressChanged(WebView view, int newProgress) { 263 ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar); 264 myProgressBar.setProgress(newProgress); 265 myProgressBar.setVisibility(newProgress == 100 ? View.GONE : View.VISIBLE); 266 } 267 } 268} 269