Home | History | Annotate | Download | only in captiveportallogin
      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.Network;
     27 import android.net.NetworkCapabilities;
     28 import android.net.NetworkRequest;
     29 import android.net.Proxy;
     30 import android.net.Uri;
     31 import android.net.http.SslError;
     32 import android.os.Bundle;
     33 import android.provider.Settings;
     34 import android.provider.Settings.Global;
     35 import android.util.ArrayMap;
     36 import android.util.Log;
     37 import android.view.Menu;
     38 import android.view.MenuItem;
     39 import android.view.View;
     40 import android.view.Window;
     41 import android.webkit.SslErrorHandler;
     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 = "connectivitycheck.android.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 = one of the CAPTIVE_PORTAL_APP_RETURN_* values below.
     66     //     RESPONSE_TOKEN   = data fragment from launching Intent
     67     private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
     68             "android.net.netmon.captive_portal_logged_in";
     69     private static final String LOGGED_IN_RESULT = "result";
     70     private static final int CAPTIVE_PORTAL_APP_RETURN_APPEASED = 0;
     71     private static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1;
     72     private static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2;
     73     private static final String RESPONSE_TOKEN = "response_token";
     74 
     75     private URL mURL;
     76     private int mNetId;
     77     private String mResponseToken;
     78     private NetworkCallback mNetworkCallback;
     79 
     80     @Override
     81     protected void onCreate(Bundle savedInstanceState) {
     82         super.onCreate(savedInstanceState);
     83 
     84         String server = Settings.Global.getString(getContentResolver(), "captive_portal_server");
     85         if (server == null) server = DEFAULT_SERVER;
     86         try {
     87             mURL = new URL("http", server, "/generate_204");
     88             final Uri dataUri = getIntent().getData();
     89             if (!dataUri.getScheme().equals("netid")) {
     90                 throw new MalformedURLException();
     91             }
     92             mNetId = Integer.parseInt(dataUri.getSchemeSpecificPart());
     93             mResponseToken = dataUri.getFragment();
     94         } catch (MalformedURLException|NumberFormatException e) {
     95             // System misconfigured, bail out in a way that at least provides network access.
     96             done(CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS);
     97         }
     98 
     99         final ConnectivityManager cm = ConnectivityManager.from(this);
    100         final Network network = new Network(mNetId);
    101         // Also initializes proxy system properties.
    102         cm.setProcessDefaultNetwork(network);
    103 
    104         // Proxy system properties must be initialized before setContentView is called because
    105         // setContentView initializes the WebView logic which in turn reads the system properties.
    106         setContentView(R.layout.activity_captive_portal_login);
    107 
    108         getActionBar().setDisplayShowHomeEnabled(false);
    109 
    110         // Exit app if Network disappears.
    111         final NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(network);
    112         if (networkCapabilities == null) {
    113             finish();
    114             return;
    115         }
    116         mNetworkCallback = new NetworkCallback() {
    117             @Override
    118             public void onLost(Network lostNetwork) {
    119                 if (network.equals(lostNetwork)) done(CAPTIVE_PORTAL_APP_RETURN_UNWANTED);
    120             }
    121         };
    122         final NetworkRequest.Builder builder = new NetworkRequest.Builder();
    123         for (int transportType : networkCapabilities.getTransportTypes()) {
    124             builder.addTransportType(transportType);
    125         }
    126         cm.registerNetworkCallback(builder.build(), mNetworkCallback);
    127 
    128         final WebView myWebView = (WebView) findViewById(R.id.webview);
    129         myWebView.clearCache(true);
    130         WebSettings webSettings = myWebView.getSettings();
    131         webSettings.setJavaScriptEnabled(true);
    132         myWebView.setWebViewClient(new MyWebViewClient());
    133         myWebView.setWebChromeClient(new MyWebChromeClient());
    134         // Start initial page load so WebView finishes loading proxy settings.
    135         // Actual load of mUrl is initiated by MyWebViewClient.
    136         myWebView.loadData("", "text/html", null);
    137     }
    138 
    139     // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
    140     private void setWebViewProxy() {
    141         LoadedApk loadedApk = getApplication().mLoadedApk;
    142         try {
    143             Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
    144             receiversField.setAccessible(true);
    145             ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
    146             for (Object receiverMap : receivers.values()) {
    147                 for (Object rec : ((ArrayMap) receiverMap).keySet()) {
    148                     Class clazz = rec.getClass();
    149                     if (clazz.getName().contains("ProxyChangeListener")) {
    150                         Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
    151                                 Intent.class);
    152                         Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
    153                         onReceiveMethod.invoke(rec, getApplicationContext(), intent);
    154                         Log.v(TAG, "Prompting WebView proxy reload.");
    155                     }
    156                 }
    157             }
    158         } catch (Exception e) {
    159             Log.e(TAG, "Exception while setting WebView proxy: " + e);
    160         }
    161     }
    162 
    163     private void done(int result) {
    164         if (mNetworkCallback != null) {
    165             ConnectivityManager.from(this).unregisterNetworkCallback(mNetworkCallback);
    166         }
    167         Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
    168         intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId));
    169         intent.putExtra(LOGGED_IN_RESULT, String.valueOf(result));
    170         intent.putExtra(RESPONSE_TOKEN, mResponseToken);
    171         sendBroadcast(intent);
    172         finish();
    173     }
    174 
    175     @Override
    176     public boolean onCreateOptionsMenu(Menu menu) {
    177         getMenuInflater().inflate(R.menu.captive_portal_login, menu);
    178         return true;
    179     }
    180 
    181     @Override
    182     public void onBackPressed() {
    183         WebView myWebView = (WebView) findViewById(R.id.webview);
    184         if (myWebView.canGoBack()) {
    185             myWebView.goBack();
    186         } else {
    187             super.onBackPressed();
    188         }
    189     }
    190 
    191     @Override
    192     public boolean onOptionsItemSelected(MenuItem item) {
    193         int id = item.getItemId();
    194         if (id == R.id.action_use_network) {
    195             done(CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS);
    196             return true;
    197         }
    198         if (id == R.id.action_do_not_use_network) {
    199             done(CAPTIVE_PORTAL_APP_RETURN_UNWANTED);
    200             return true;
    201         }
    202         return super.onOptionsItemSelected(item);
    203     }
    204 
    205     private void testForCaptivePortal() {
    206         new Thread(new Runnable() {
    207             public void run() {
    208                 // Give time for captive portal to open.
    209                 try {
    210                     Thread.sleep(1000);
    211                 } catch (InterruptedException e) {
    212                 }
    213                 HttpURLConnection urlConnection = null;
    214                 int httpResponseCode = 500;
    215                 try {
    216                     urlConnection = (HttpURLConnection) mURL.openConnection();
    217                     urlConnection.setInstanceFollowRedirects(false);
    218                     urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
    219                     urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
    220                     urlConnection.setUseCaches(false);
    221                     urlConnection.getInputStream();
    222                     httpResponseCode = urlConnection.getResponseCode();
    223                 } catch (IOException e) {
    224                 } finally {
    225                     if (urlConnection != null) urlConnection.disconnect();
    226                 }
    227                 if (httpResponseCode == 204) {
    228                     done(CAPTIVE_PORTAL_APP_RETURN_APPEASED);
    229                 }
    230             }
    231         }).start();
    232     }
    233 
    234     private class MyWebViewClient extends WebViewClient {
    235         private boolean firstPageLoad = true;
    236 
    237         @Override
    238         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    239             if (firstPageLoad) return;
    240             testForCaptivePortal();
    241         }
    242 
    243         @Override
    244         public void onPageFinished(WebView view, String url) {
    245             if (firstPageLoad) {
    246                 firstPageLoad = false;
    247                 // Now that WebView has loaded at least one page we know it has read in the proxy
    248                 // settings.  Now prompt the WebView read the Network-specific proxy settings.
    249                 setWebViewProxy();
    250                 // Load the real page.
    251                 view.loadUrl(mURL.toString());
    252                 return;
    253             }
    254             testForCaptivePortal();
    255         }
    256 
    257         // A web page consisting of a large broken lock icon to indicate SSL failure.
    258         final static String SSL_ERROR_HTML = "<!DOCTYPE html><html><head><style>" +
    259                 "html { width:100%; height:100%; " +
    260                 "       background:url(locked_page.png) center center no-repeat; }" +
    261                 "</style></head><body></body></html>";
    262 
    263         @Override
    264         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    265             Log.w(TAG, "SSL error; displaying broken lock icon.");
    266             view.loadDataWithBaseURL("file:///android_asset/", SSL_ERROR_HTML, "text/HTML",
    267                     "UTF-8", null);
    268         }
    269     }
    270 
    271     private class MyWebChromeClient extends WebChromeClient {
    272         @Override
    273         public void onProgressChanged(WebView view, int newProgress) {
    274             ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
    275             myProgressBar.setProgress(newProgress);
    276             myProgressBar.setVisibility(newProgress == 100 ? View.GONE : View.VISIBLE);
    277         }
    278     }
    279 }
    280