• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.captiveportallogin;
18 
19 import android.app.Activity;
20 import android.app.LoadedApk;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.net.ConnectivityManager;
25 import android.net.ConnectivityManager.NetworkCallback;
26 import android.net.LinkProperties;
27 import android.net.Network;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.net.Proxy;
31 import android.net.ProxyInfo;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.provider.Settings.Global;
36 import android.util.ArrayMap;
37 import android.util.Log;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.Window;
42 import android.webkit.WebChromeClient;
43 import android.webkit.WebSettings;
44 import android.webkit.WebView;
45 import android.webkit.WebViewClient;
46 import android.widget.ProgressBar;
47 
48 import java.io.IOException;
49 import java.net.HttpURLConnection;
50 import java.net.MalformedURLException;
51 import java.net.URL;
52 import java.lang.InterruptedException;
53 import java.lang.reflect.Field;
54 import java.lang.reflect.Method;
55 
56 public 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
onCreate(Bundle savedInstanceState)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.
setWebViewProxy()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 
done(boolean use_network)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
onCreateOptionsMenu(Menu menu)178     public boolean onCreateOptionsMenu(Menu menu) {
179         getMenuInflater().inflate(R.menu.captive_portal_login, menu);
180         return true;
181     }
182 
183     @Override
onBackPressed()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
onOptionsItemSelected(MenuItem item)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 
testForCaptivePortal()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
onPageStarted(WebView view, String url, Bitmap favicon)240         public void onPageStarted(WebView view, String url, Bitmap favicon) {
241             if (firstPageLoad) return;
242             testForCaptivePortal();
243         }
244 
245         @Override
onPageFinished(WebView view, String url)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
onProgressChanged(WebView view, int newProgress)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