1869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen/* 2869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * Copyright (C) 2014 The Android Open Source Project 3869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * 4869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * Licensed under the Apache License, Version 2.0 (the "License"); 5869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * you may not use this file except in compliance with the License. 6869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * You may obtain a copy of the License at 7869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * 8869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * http://www.apache.org/licenses/LICENSE-2.0 9869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * 10869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * Unless required by applicable law or agreed to in writing, software 11869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * distributed under the License is distributed on an "AS IS" BASIS, 12869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * See the License for the specific language governing permissions and 14869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen * limitations under the License. 15869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen */ 16869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 17869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenpackage com.android.captiveportallogin; 18869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 199c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VANimport static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 209c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VANimport static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME; 219c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN 22869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.app.Activity; 2388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.app.LoadedApk; 2488eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.content.Context; 25869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.content.Intent; 26869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.graphics.Bitmap; 2749e3edff5156f471819e4ea2a88994bca70bd870Paul Jensenimport android.net.CaptivePortal; 28869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.net.ConnectivityManager; 298df099df1516d23c113be3121635dcd34984a4a0Paul Jensenimport android.net.ConnectivityManager.NetworkCallback; 30869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.net.Network; 318df099df1516d23c113be3121635dcd34984a4a0Paul Jensenimport android.net.NetworkCapabilities; 328df099df1516d23c113be3121635dcd34984a4a0Paul Jensenimport android.net.NetworkRequest; 3388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.net.Proxy; 3488eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.net.Uri; 359c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VANimport android.net.captiveportal.CaptivePortalProbeSpec; 36c43d2f5b1f12dadd8c87c069cd4a13cfbc481699Erik Klineimport android.net.dns.ResolvUtil; 37fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensenimport android.net.http.SslError; 382dcccbcc0b7a2d0057902959b0ac9beb0eb2cf32Chalard Jeanimport android.net.wifi.WifiInfo; 3912df465997c1be51c6802acad2dcf20f010c3576Hugo Benichiimport android.os.Build; 40869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.os.Bundle; 41869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.provider.Settings; 4294bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jeanimport android.support.v4.widget.SwipeRefreshLayout; 43c1b4efb4bea2a0364822a8160f74e4557b18e913Adam Newmanimport android.text.TextUtils; 4488eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.util.ArrayMap; 4588eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport android.util.Log; 4665636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensenimport android.util.TypedValue; 4704d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichiimport android.util.SparseArray; 48869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.view.Menu; 49869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.view.MenuItem; 50a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichiimport android.view.View; 51c3c95bab8ec7f3ef4e7649b5d24ea6525a407c44Lorenzo Colittiimport android.webkit.CookieManager; 52fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensenimport android.webkit.SslErrorHandler; 53869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.webkit.WebChromeClient; 54869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.webkit.WebSettings; 55869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.webkit.WebView; 5604d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichiimport android.webkit.WebView; 57869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport android.webkit.WebViewClient; 588f333f19222ac9415152e31f10e0df2b571b0b77Paul Jensenimport android.widget.ProgressBar; 595344a4abdf239a19485a9c858b6cc3be96002eacPaul Jensenimport android.widget.TextView; 60869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 619e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichiimport com.android.internal.logging.MetricsLogger; 629e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichiimport com.android.internal.logging.nano.MetricsProto.MetricsEvent; 639e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi 64869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport java.io.IOException; 65869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport java.net.HttpURLConnection; 66869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport java.net.MalformedURLException; 67869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport java.net.URL; 68869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenimport java.lang.InterruptedException; 6988eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport java.lang.reflect.Field; 7088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensenimport java.lang.reflect.Method; 7112df465997c1be51c6802acad2dcf20f010c3576Hugo Benichiimport java.util.Objects; 7265636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensenimport java.util.Random; 73a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichiimport java.util.concurrent.atomic.AtomicBoolean; 74869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 75869868be653cb8eedd338e8347dfee1520d38cecPaul Jensenpublic class CaptivePortalLoginActivity extends Activity { 767f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName(); 777f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi private static final boolean DBG = true; 7860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi private static final boolean VDBG = false; 797f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi 80869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen private static final int SOCKET_TIMEOUT_MS = 10000; 81869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 829e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi private enum Result { 839e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED), 849e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi UNWANTED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED), 859e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi WANTED_AS_IS(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS); 869e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi 879e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi final int metricsEvent; 889e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi Result(int metricsEvent) { this.metricsEvent = metricsEvent; } 899e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi }; 90869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 917f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi private URL mUrl; 929c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN private CaptivePortalProbeSpec mProbeSpec; 932c02197bdd12378c7b2f8ee4bcaa625b2c564edeHugo Benichi private String mUserAgent; 9425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private Network mNetwork; 9549e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen private CaptivePortal mCaptivePortal; 968df099df1516d23c113be3121635dcd34984a4a0Paul Jensen private NetworkCallback mNetworkCallback; 9725a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private ConnectivityManager mCm; 9865636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen private boolean mLaunchBrowser = false; 99e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen private MyWebViewClient mWebViewClient; 10094bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean private SwipeRefreshLayout mSwipeRefreshLayout; 101a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi // Ensures that done() happens once exactly, handling concurrent callers with atomic operations. 102a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi private final AtomicBoolean isDone = new AtomicBoolean(false); 103869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 104869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 105869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen protected void onCreate(Bundle savedInstanceState) { 106869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen super.onCreate(savedInstanceState); 1079e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi 1089e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi logMetricsEvent(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY); 1099e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi 11025a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mCm = ConnectivityManager.from(this); 11125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); 11249e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); 113ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi mUserAgent = 114ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); 1157f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi mUrl = getUrl(); 1167f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (mUrl == null) { 1177f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi // getUrl() failed to parse the url provided in the intent: bail out in a way that 1187f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi // at least provides network access. 1197f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi done(Result.WANTED_AS_IS); 1207f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi return; 1217f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 1227f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (DBG) { 1237f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi Log.d(TAG, String.format("onCreate for %s", mUrl.toString())); 1247f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 125869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 1269c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN final String spec = getIntent().getStringExtra(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC); 1279c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN try { 1289c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN mProbeSpec = CaptivePortalProbeSpec.parseSpecOrNull(spec); 1299c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN } catch (Exception e) { 1309c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN // Make extra sure that invalid configurations do not cause crashes 1319c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN mProbeSpec = null; 1329c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN } 1339c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN 134e0bef71662d81caaaa0d7214fb0bef5d39996a69Paul Jensen // Also initializes proxy system properties. 13525a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mCm.bindProcessToNetwork(mNetwork); 136c43d2f5b1f12dadd8c87c069cd4a13cfbc481699Erik Kline mCm.setProcessDefaultNetworkForHostResolution( 137c43d2f5b1f12dadd8c87c069cd4a13cfbc481699Erik Kline ResolvUtil.getNetworkWithUseLocalNameserversFlag(mNetwork)); 13888eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen 13988eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Proxy system properties must be initialized before setContentView is called because 14088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // setContentView initializes the WebView logic which in turn reads the system properties. 14188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen setContentView(R.layout.activity_captive_portal_login); 14288eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen 1438df099df1516d23c113be3121635dcd34984a4a0Paul Jensen // Exit app if Network disappears. 14425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen final NetworkCapabilities networkCapabilities = mCm.getNetworkCapabilities(mNetwork); 1458df099df1516d23c113be3121635dcd34984a4a0Paul Jensen if (networkCapabilities == null) { 1466a776c8317138cf4a3013addc1bd1a462f6dc1ebPaul Jensen finishAndRemoveTask(); 1478df099df1516d23c113be3121635dcd34984a4a0Paul Jensen return; 1488df099df1516d23c113be3121635dcd34984a4a0Paul Jensen } 1498df099df1516d23c113be3121635dcd34984a4a0Paul Jensen mNetworkCallback = new NetworkCallback() { 1508df099df1516d23c113be3121635dcd34984a4a0Paul Jensen @Override 1518df099df1516d23c113be3121635dcd34984a4a0Paul Jensen public void onLost(Network lostNetwork) { 15225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen if (mNetwork.equals(lostNetwork)) done(Result.UNWANTED); 1538df099df1516d23c113be3121635dcd34984a4a0Paul Jensen } 1548df099df1516d23c113be3121635dcd34984a4a0Paul Jensen }; 1558df099df1516d23c113be3121635dcd34984a4a0Paul Jensen final NetworkRequest.Builder builder = new NetworkRequest.Builder(); 1568df099df1516d23c113be3121635dcd34984a4a0Paul Jensen for (int transportType : networkCapabilities.getTransportTypes()) { 1578df099df1516d23c113be3121635dcd34984a4a0Paul Jensen builder.addTransportType(transportType); 1588df099df1516d23c113be3121635dcd34984a4a0Paul Jensen } 15925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen mCm.registerNetworkCallback(builder.build(), mNetworkCallback); 160869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 161a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getActionBar().setDisplayShowHomeEnabled(false); 162a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getActionBar().setElevation(0); // remove shadow 163a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getActionBar().setTitle(getHeaderTitle()); 164a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getActionBar().setSubtitle(""); 165a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 166a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi final WebView webview = getWebview(); 167a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi webview.clearCache(true); 168c3c95bab8ec7f3ef4e7649b5d24ea6525a407c44Lorenzo Colitti CookieManager.getInstance().setAcceptThirdPartyCookies(webview, true); 169a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi WebSettings webSettings = webview.getSettings(); 170869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen webSettings.setJavaScriptEnabled(true); 171b55bf38351fc06d267735e8e377d4049c2a7b5d3Lorenzo Colitti webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); 17294c5fb342bbb04bcf099807752243c84b26b9a9bHugo Benichi webSettings.setUseWideViewPort(true); 17394c5fb342bbb04bcf099807752243c84b26b9a9bHugo Benichi webSettings.setLoadWithOverviewMode(true); 17494c5fb342bbb04bcf099807752243c84b26b9a9bHugo Benichi webSettings.setSupportZoom(true); 17594c5fb342bbb04bcf099807752243c84b26b9a9bHugo Benichi webSettings.setBuiltInZoomControls(true); 17694c5fb342bbb04bcf099807752243c84b26b9a9bHugo Benichi webSettings.setDisplayZoomControls(false); 177e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen mWebViewClient = new MyWebViewClient(); 178a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi webview.setWebViewClient(mWebViewClient); 179a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi webview.setWebChromeClient(new MyWebChromeClient()); 18088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Start initial page load so WebView finishes loading proxy settings. 18188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Actual load of mUrl is initiated by MyWebViewClient. 182a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi webview.loadData("", "text/html", null); 18394bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean 18494bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean mSwipeRefreshLayout = findViewById(R.id.swipe_refresh); 18594bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean mSwipeRefreshLayout.setOnRefreshListener(() -> { 18694bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean webview.reload(); 18794bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean mSwipeRefreshLayout.setRefreshing(true); 18894bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean }); 18994bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean 19088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } 19188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen 19288eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties. 19388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen private void setWebViewProxy() { 19488eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen LoadedApk loadedApk = getApplication().mLoadedApk; 19588eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen try { 19688eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Field receiversField = LoadedApk.class.getDeclaredField("mReceivers"); 19788eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen receiversField.setAccessible(true); 19888eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk); 19988eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen for (Object receiverMap : receivers.values()) { 20088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen for (Object rec : ((ArrayMap) receiverMap).keySet()) { 20188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Class clazz = rec.getClass(); 20288eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen if (clazz.getName().contains("ProxyChangeListener")) { 20388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, 20488eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Intent.class); 20588eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); 20688eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen onReceiveMethod.invoke(rec, getApplicationContext(), intent); 20788eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Log.v(TAG, "Prompting WebView proxy reload."); 20888eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } 20988eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } 21088eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } 21188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } catch (Exception e) { 21288eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen Log.e(TAG, "Exception while setting WebView proxy: " + e); 21388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen } 214869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 215869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 21625a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen private void done(Result result) { 217a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi if (isDone.getAndSet(true)) { 218a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi // isDone was already true: done() already called 219a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi return; 220a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi } 2217f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (DBG) { 2227f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString())); 2237f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 2249e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi logMetricsEvent(result.metricsEvent); 22525a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen switch (result) { 22625a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen case DISMISSED: 22749e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen mCaptivePortal.reportCaptivePortalDismissed(); 22825a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen break; 22925a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen case UNWANTED: 23049e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen mCaptivePortal.ignoreNetwork(); 23125a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen break; 23225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen case WANTED_AS_IS: 23349e3edff5156f471819e4ea2a88994bca70bd870Paul Jensen mCaptivePortal.useNetwork(); 23425a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen break; 23571b645fe9cb8106dfcbf025a3fd7f58698c051bbPaul Jensen } 2366a776c8317138cf4a3013addc1bd1a462f6dc1ebPaul Jensen finishAndRemoveTask(); 237869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 238869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 239869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 240869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen public boolean onCreateOptionsMenu(Menu menu) { 241869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen getMenuInflater().inflate(R.menu.captive_portal_login, menu); 242869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen return true; 243869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 244869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 245869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 246b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen public void onBackPressed() { 24751efddbd3bb304de2dd47fa8cd1114ac555958bbAlan Viverette WebView myWebView = findViewById(R.id.webview); 248e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen if (myWebView.canGoBack() && mWebViewClient.allowBack()) { 249b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen myWebView.goBack(); 250b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen } else { 251b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen super.onBackPressed(); 252b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen } 253b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen } 254b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen 255b6ea9ee6fe6fc205f4f8be593ca993d594e8d504Paul Jensen @Override 256869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen public boolean onOptionsItemSelected(MenuItem item) { 2577f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi final Result result; 2587f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi final String action; 2597f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi final int id = item.getItemId(); 2607f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi switch (id) { 2617f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi case R.id.action_use_network: 2627f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi result = Result.WANTED_AS_IS; 2637f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi action = "USE_NETWORK"; 2647f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi break; 2657f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi case R.id.action_do_not_use_network: 2667f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi result = Result.UNWANTED; 2677f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi action = "DO_NOT_USE_NETWORK"; 2687f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi break; 2697f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi default: 2707f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi return super.onOptionsItemSelected(item); 271869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 2727f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (DBG) { 2737f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi Log.d(TAG, String.format("onOptionsItemSelect %s for %s", action, mUrl.toString())); 274869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 2757f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi done(result); 2767f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi return true; 277869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 278869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 279868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen @Override 280868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen public void onDestroy() { 281868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen super.onDestroy(); 282868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen if (mNetworkCallback != null) { 283a173a63a6cf9c94c511d14d75648f55525ce7006Hugo Benichi // mNetworkCallback is not null if mUrl is not null. 284868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen mCm.unregisterNetworkCallback(mNetworkCallback); 285868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen } 28665636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen if (mLaunchBrowser) { 28765636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // Give time for this network to become default. After 500ms just proceed. 28865636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen for (int i = 0; i < 5; i++) { 28965636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // TODO: This misses when mNetwork underlies a VPN. 29065636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen if (mNetwork.equals(mCm.getActiveNetwork())) break; 29165636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen try { 29265636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen Thread.sleep(100); 29365636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } catch (InterruptedException e) { 29465636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } 29565636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } 2967f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi final String url = mUrl.toString(); 2977f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (DBG) { 2987f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi Log.d(TAG, "starting activity with intent ACTION_VIEW for " + url); 2997f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 3007f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 3017f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 3027f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 3037f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi 3047f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi private URL getUrl() { 3057f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); 3067f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi if (url == null) { 3077f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi url = mCm.getCaptivePortalServerUrl(); 3087f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } 309a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi return makeURL(url); 310a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 311a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 312a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi private static URL makeURL(String url) { 3137f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi try { 3147f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi return new URL(url); 3157f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi } catch (MalformedURLException e) { 316a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi Log.e(TAG, "Invalid URL " + url); 31765636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } 3187f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi return null; 319868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen } 320868f6243bc6021465938a7b764bef8fd001cb39cPaul Jensen 32112df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi private static String host(URL url) { 32212df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi if (url == null) { 32312df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return null; 32412df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi } 32512df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return url.getHost(); 32612df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi } 32712df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi 32812df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi private static String sanitizeURL(URL url) { 32912df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi // In non-Debug build, only show host to avoid leaking private info. 33012df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return Build.IS_DEBUGGABLE ? Objects.toString(url) : host(url); 33112df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi } 33212df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi 333869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen private void testForCaptivePortal() { 3342c02197bdd12378c7b2f8ee4bcaa625b2c564edeHugo Benichi // TODO: reuse NetworkMonitor facilities for consistent captive portal detection. 335869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen new Thread(new Runnable() { 336869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen public void run() { 337d324dceceff5fc85682212092b8321414bda3ad6Erik Kline final Network network = ResolvUtil.makeNetworkWithPrivateDnsBypass(mNetwork); 338869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen // Give time for captive portal to open. 339869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen try { 340869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen Thread.sleep(1000); 341869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } catch (InterruptedException e) { 342869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 343869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen HttpURLConnection urlConnection = null; 344869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen int httpResponseCode = 500; 3459c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN String locationHeader = null; 346869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen try { 347d324dceceff5fc85682212092b8321414bda3ad6Erik Kline urlConnection = (HttpURLConnection) network.openConnection(mUrl); 348869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen urlConnection.setInstanceFollowRedirects(false); 349869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 350869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 351869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen urlConnection.setUseCaches(false); 3522c02197bdd12378c7b2f8ee4bcaa625b2c564edeHugo Benichi if (mUserAgent != null) { 3532c02197bdd12378c7b2f8ee4bcaa625b2c564edeHugo Benichi urlConnection.setRequestProperty("User-Agent", mUserAgent); 3542c02197bdd12378c7b2f8ee4bcaa625b2c564edeHugo Benichi } 355ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi // cannot read request header after connection 356ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi String requestHeader = urlConnection.getRequestProperties().toString(); 357ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi 358869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen urlConnection.getInputStream(); 359869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen httpResponseCode = urlConnection.getResponseCode(); 3609c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN locationHeader = urlConnection.getHeaderField(HTTP_LOCATION_HEADER_NAME); 361ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi if (DBG) { 362ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi Log.d(TAG, "probe at " + mUrl + 363ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi " ret=" + httpResponseCode + 364ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi " request=" + requestHeader + 365ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi " headers=" + urlConnection.getHeaderFields()); 366ec88fd6708c14515f75d0f9de24e505e62fc3550Hugo Benichi } 367869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } catch (IOException e) { 368869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } finally { 369869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen if (urlConnection != null) urlConnection.disconnect(); 370869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 3719c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN if (isDismissed(httpResponseCode, locationHeader, mProbeSpec)) { 37225a217c0fbda9bbaf58ec08b91115e99f73b727fPaul Jensen done(Result.DISMISSED); 373869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 374869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 375869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen }).start(); 376869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 377869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 3789c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN private static boolean isDismissed( 3799c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN int httpResponseCode, String locationHeader, CaptivePortalProbeSpec probeSpec) { 3809c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN return (probeSpec != null) 3819c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN ? probeSpec.getResult(httpResponseCode, locationHeader).isSuccessful() 3829c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN : (httpResponseCode == 204); 3839c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN } 3849c5c3681158d9f9247f3876b7e7c5204871ff89bRemi NGUYEN VAN 385869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen private class MyWebViewClient extends WebViewClient { 3865344a4abdf239a19485a9c858b6cc3be96002eacPaul Jensen private static final String INTERNAL_ASSETS = "file:///android_asset/"; 38760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi 38865636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen private final String mBrowserBailOutToken = Long.toString(new Random().nextLong()); 38965636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // How many Android device-independent-pixels per scaled-pixel 39065636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp) 39165636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1, 39265636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen getResources().getDisplayMetrics()) / 39365636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, 39465636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen getResources().getDisplayMetrics()); 395e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen private int mPagesLoaded; 39612df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi // the host of the page that this webview is currently loading. Can be null when undefined. 39712df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi private String mHostname; 398e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen 399e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // If we haven't finished cleaning up the history, don't allow going back. 400e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen public boolean allowBack() { 401e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen return mPagesLoaded > 1; 402e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen } 40388eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen 404869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 40512df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi public void onPageStarted(WebView view, String urlString, Bitmap favicon) { 40612df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi if (urlString.contains(mBrowserBailOutToken)) { 40765636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen mLaunchBrowser = true; 40865636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen done(Result.WANTED_AS_IS); 40965636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen return; 41065636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } 411e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // The first page load is used only to cause the WebView to 412e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // fetch the proxy settings. Don't update the URL bar, and 413e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // don't check if the captive portal is still there. 41412df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi if (mPagesLoaded == 0) { 41512df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return; 41612df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi } 41712df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi final URL url = makeURL(urlString); 41804d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi Log.d(TAG, "onPageStarted: " + sanitizeURL(url)); 41912df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi mHostname = host(url); 420e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // For internally generated pages, leave URL bar listing prior URL as this is the URL 421e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // the page refers to. 42212df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi if (!urlString.startsWith(INTERNAL_ASSETS)) { 42312df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi String subtitle = (url != null) ? getHeaderSubtitle(url) : urlString; 42412df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi getActionBar().setSubtitle(subtitle); 425e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen } 426a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getProgressBar().setVisibility(View.VISIBLE); 427869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen testForCaptivePortal(); 428869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 429869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 430869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 431869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen public void onPageFinished(WebView view, String url) { 432e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen mPagesLoaded++; 433a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getProgressBar().setVisibility(View.INVISIBLE); 43494bc48f7bbff4772de967bcfc3effd4f710503c2Chalard Jean mSwipeRefreshLayout.setRefreshing(false); 435e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen if (mPagesLoaded == 1) { 43688eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Now that WebView has loaded at least one page we know it has read in the proxy 43788eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // settings. Now prompt the WebView read the Network-specific proxy settings. 43888eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen setWebViewProxy(); 43988eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen // Load the real page. 4407f086e162b9000fd471f3450ae53fe1261f58993Hugo Benichi view.loadUrl(mUrl.toString()); 44188eb0fa8eec7da1b7a3bd39f9d9844909911bc64Paul Jensen return; 442e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen } else if (mPagesLoaded == 2) { 443e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen // Prevent going back to empty first page. 44497640400c4aa6bf05d5606ea749b54ad63de97cdsusnata // Fix for missing focus, see b/62449959 for details. Remove it once we get a 44597640400c4aa6bf05d5606ea749b54ad63de97cdsusnata // newer version of WebView (60.x.y). 44697640400c4aa6bf05d5606ea749b54ad63de97cdsusnata view.requestFocus(); 447e836b6847af968460f36a4e6649b8cb6f6da18dbPaul Jensen view.clearHistory(); 4485344a4abdf239a19485a9c858b6cc3be96002eacPaul Jensen } 449869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen testForCaptivePortal(); 450869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 451fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen 45265636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // Convert Android scaled-pixels (sp) to HTML size. 45365636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen private String sp(int sp) { 45465636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // Convert sp to dp's. 45565636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen float dp = sp * mDpPerSp; 45665636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // Apply a scale factor to make things look right. 45765636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen dp *= 1.3; 45865636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen // Convert dp's to HTML size. 45960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi // HTML px's are scaled just like dp's, so just add "px" suffix. 46060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi return Integer.toString((int)dp) + "px"; 46165636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen } 46265636fb23c86e546dc4ce584481fa58bf72e4945Paul Jensen 463fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen // A web page consisting of a large broken lock icon to indicate SSL failure. 464fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen 465fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen @Override 466fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 46712df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi final URL url = makeURL(error.getUrl()); 46812df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi final String host = host(url); 46912df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi Log.d(TAG, String.format("SSL error: %s, url: %s, certificate: %s", 47004d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi sslErrorName(error), sanitizeURL(url), error.getCertificate())); 47112df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi if (url == null || !Objects.equals(host, mHostname)) { 47212df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi // Ignore ssl errors for resources coming from a different hostname than the page 47312df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi // that we are currently loading, and only cancel the request. 47412df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi handler.cancel(); 47512df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return; 47612df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi } 4779e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR); 47860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi final String sslErrorPage = makeSslErrorPage(); 47960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi view.loadDataWithBaseURL(INTERNAL_ASSETS, sslErrorPage, "text/HTML", "UTF-8", null); 48060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi } 48160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi 48260d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi private String makeSslErrorPage() { 48360d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi final String warningMsg = getString(R.string.ssl_error_warning); 48460d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi final String exampleMsg = getString(R.string.ssl_error_example); 48560d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi final String continueMsg = getString(R.string.ssl_error_continue); 48660d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi return String.join("\n", 48760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "<html>", 48860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "<head>", 48960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">", 49060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <style>", 49160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " body {", 49260d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " background-color:#fafafa;", 49360d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " margin:auto;", 49460d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " width:80%;", 49560d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " margin-top: 96px", 49660d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " }", 49760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " img {", 49860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " height:48px;", 49960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " width:48px;", 50060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " }", 50160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " div.warn {", 50260d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " font-size:" + sp(16) + ";", 50360d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " line-height:1.28;", 50460d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " margin-top:16px;", 50560d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " opacity:0.87;", 50660d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " }", 50760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " div.example {", 50860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " font-size:" + sp(14) + ";", 50960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " line-height:1.21905;", 51060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " margin-top:16px;", 51160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " opacity:0.54;", 51260d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " }", 51360d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " a {", 51460d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " color:#4285F4;", 51560d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " display:inline-block;", 51660d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " font-size:" + sp(14) + ";", 51760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " font-weight:bold;", 51860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " height:48px;", 51960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " margin-top:24px;", 52060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " text-decoration:none;", 52160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " text-transform:uppercase;", 52260d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " }", 52360d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " </style>", 52460d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "</head>", 52560d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "<body>", 52660d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <p><img src=quantum_ic_warning_amber_96.png><br>", 52760d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <div class=warn>" + warningMsg + "</div>", 52860d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <div class=example>" + exampleMsg + "</div>", 52960d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi " <a href=" + mBrowserBailOutToken + ">" + continueMsg + "</a>", 53060d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "</body>", 53160d5f46d89038a6a46c38ac0e57d0762a67732cbHugo Benichi "</html>"); 532fc8022f8cfffded3d94baef3ba5e5ce936799b06Paul Jensen } 533fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen 534fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen @Override 535fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen public boolean shouldOverrideUrlLoading (WebView view, String url) { 536fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen if (url.startsWith("tel:")) { 537fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url))); 538fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen return true; 539fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen } 540fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen return false; 541fd54da9b7ccf8218bb99cbbe540fd71ea411508bPaul Jensen } 542869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 543869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen 544869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen private class MyWebChromeClient extends WebChromeClient { 545869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen @Override 546869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen public void onProgressChanged(WebView view, int newProgress) { 547a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi getProgressBar().setProgress(newProgress); 548a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 549a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 550a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 551a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi private ProgressBar getProgressBar() { 552a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi return findViewById(R.id.progress_bar); 553a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 554a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 555a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi private WebView getWebview() { 556a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi return findViewById(R.id.webview); 557a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 558a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 559a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi private String getHeaderTitle() { 5603a222974e9cdbb120d22c439580401a5d63b51b2Hugo Benichi NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork); 5612dcccbcc0b7a2d0057902959b0ac9beb0eb2cf32Chalard Jean if (nc == null || TextUtils.isEmpty(nc.getSSID()) 5622dcccbcc0b7a2d0057902959b0ac9beb0eb2cf32Chalard Jean || !nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { 5633a222974e9cdbb120d22c439580401a5d63b51b2Hugo Benichi return getString(R.string.action_bar_label); 5643a222974e9cdbb120d22c439580401a5d63b51b2Hugo Benichi } 5652dcccbcc0b7a2d0057902959b0ac9beb0eb2cf32Chalard Jean return getString(R.string.action_bar_title, WifiInfo.removeDoubleQuotes(nc.getSSID())); 566a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi } 567a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi 56812df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi private String getHeaderSubtitle(URL url) { 56912df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi String host = host(url); 570a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi final String https = "https"; 571a206649a6f66f16cc56db2f4e32b846d9b03501cHugo Benichi if (https.equals(url.getProtocol())) { 57212df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return https + "://" + host; 573869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 57412df465997c1be51c6802acad2dcf20f010c3576Hugo Benichi return host; 575869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen } 5769e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi 5779e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi private void logMetricsEvent(int event) { 5789e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi MetricsLogger.action(this, event, getPackageName()); 5799e8ab43ab22555acff9fefca2ed433425c92fb87Hugo Benichi } 58004d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi 58104d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi private static final SparseArray<String> SSL_ERRORS = new SparseArray<>(); 58204d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi static { 58304d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_NOTYETVALID, "SSL_NOTYETVALID"); 58404d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_EXPIRED, "SSL_EXPIRED"); 58504d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_IDMISMATCH, "SSL_IDMISMATCH"); 58604d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_UNTRUSTED, "SSL_UNTRUSTED"); 58704d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_DATE_INVALID, "SSL_DATE_INVALID"); 58804d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi SSL_ERRORS.put(SslError.SSL_INVALID, "SSL_INVALID"); 58904d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi } 59004d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi 59104d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi private static String sslErrorName(SslError error) { 59204d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi return SSL_ERRORS.get(error.getPrimaryError(), "UNKNOWN"); 59304d78601ab0bbf7deee9ef97526fd9d45587aec1Hugo Benichi } 594869868be653cb8eedd338e8347dfee1520d38cecPaul Jensen} 595