Enable network validations and add app to handle captive portal login.

Network validation prevents networks claiming to provide internet connectivity
from becoming the default network in cases where internet connectivity is not
found to actually exist.
If a captive portal is encountered the appropriate broadcasts and notifications
are surfaced to allow apps to handle signing in.  If no app handles signing in,
my system app will handle it.

Bug:15409233
Bug:15409354

Change-Id: Ie240d7eac4bdbab8cc7578782bd72d8b26de7951
diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk
new file mode 100644
index 0000000..576debc
--- /dev/null
+++ b/packages/CaptivePortalLogin/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CaptivePortalLogin
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
new file mode 100644
index 0000000..5f78afe
--- /dev/null
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.captiveportallogin" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:label="@string/app_name" >
+        <activity
+            android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
+            android:label="@string/action_bar_label"
+            android:theme="@android:style/Theme.Holo" >
+            <intent-filter>
+                <action android:name="android.intent.action.ACTION_SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="text/plain"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
new file mode 100644
index 0000000..d8f2928
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
@@ -0,0 +1,20 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity"
+    tools:ignore="MergeRootFrame">
+    <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="false"
+        android:layout_alignParentRight="false" />
+
+</RelativeLayout>
+</FrameLayout>
diff --git a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
new file mode 100644
index 0000000..1a88c5c
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
@@ -0,0 +1,15 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" >
+    <item
+        android:id="@+id/action_do_not_use_network"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/action_do_not_use_network"/>
+    <item
+        android:id="@+id/action_use_network"
+        android:orderInCategory="200"
+        android:showAsAction="never"
+        android:title="@string/action_use_network"/>
+
+</menu>
diff --git a/packages/CaptivePortalLogin/res/values/dimens.xml b/packages/CaptivePortalLogin/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/dimens.xml
@@ -0,0 +1,7 @@
+<resources>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
new file mode 100644
index 0000000..1b0f0a4
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">CaptivePortalLogin</string>
+    <string name="action_use_network">Use this network as is</string>
+    <string name="action_do_not_use_network">Do not use this network</string>
+    <string name="action_bar_label">Sign-in to network</string>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/res/values/styles.xml b/packages/CaptivePortalLogin/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
new file mode 100644
index 0000000..2c1db02
--- /dev/null
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.captiveportallogin;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.lang.InterruptedException;
+
+public class CaptivePortalLoginActivity extends Activity {
+    private static final String DEFAULT_SERVER = "clients3.google.com";
+    private static final int SOCKET_TIMEOUT_MS = 10000;
+
+    // Keep this in sync with NetworkMonitor.
+    // Intent broadcast to ConnectivityService indicating sign-in is complete.
+    // Extras:
+    //     EXTRA_TEXT       = netId
+    //     LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
+    private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
+            "android.net.netmon.captive_portal_logged_in";
+    private static final String LOGGED_IN_RESULT = "result";
+
+    private URL mURL;
+    private int mNetId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String server = Settings.Global.getString(getContentResolver(), "captive_portal_server");
+        if (server == null) server = DEFAULT_SERVER;
+        try {
+            mURL = new URL("http://" + server + "/generate_204");
+        } catch (MalformedURLException e) {
+            done(true);
+        }
+
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+        setContentView(R.layout.activity_captive_portal_login);
+
+        getActionBar().setDisplayShowHomeEnabled(false);
+
+        mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT));
+        ConnectivityManager.setProcessDefaultNetwork(new Network(mNetId));
+
+        WebView myWebView = (WebView) findViewById(R.id.webview);
+        WebSettings webSettings = myWebView.getSettings();
+        webSettings.setJavaScriptEnabled(true);
+        myWebView.setWebViewClient(new MyWebViewClient());
+        myWebView.setWebChromeClient(new MyWebChromeClient());
+        myWebView.loadUrl(mURL.toString());
+    }
+
+    private void done(boolean use_network) {
+        Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
+        intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId));
+        intent.putExtra(LOGGED_IN_RESULT, use_network ? "1" : "0");
+        sendBroadcast(intent);
+        finish();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.captive_portal_login, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+        if (id == R.id.action_use_network) {
+            done(true);
+            return true;
+        }
+        if (id == R.id.action_do_not_use_network) {
+            done(false);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void testForCaptivePortal() {
+        new Thread(new Runnable() {
+            public void run() {
+                // Give time for captive portal to open.
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                }
+                HttpURLConnection urlConnection = null;
+                int httpResponseCode = 500;
+                try {
+                    urlConnection = (HttpURLConnection) mURL.openConnection();
+                    urlConnection.setInstanceFollowRedirects(false);
+                    urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setUseCaches(false);
+                    urlConnection.getInputStream();
+                    httpResponseCode = urlConnection.getResponseCode();
+                } catch (IOException e) {
+                } finally {
+                    if (urlConnection != null) urlConnection.disconnect();
+                }
+                if (httpResponseCode == 204) {
+                    done(true);
+                }
+            }
+        }).start();
+    }
+
+    private class MyWebViewClient extends WebViewClient {
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            testForCaptivePortal();
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            testForCaptivePortal();
+        }
+    }
+
+    private class MyWebChromeClient extends WebChromeClient {
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            setProgress(newProgress*100);
+        }
+    }
+}