VpnSettings: make more fields available as advanced options.

Now users can manually override DNS search domains, DNS servers,
and forwarding routes for each VPN network.

Change-Id: I10b8e383ac19fd19d23938dff78201a71724d58f
diff --git a/res/layout/vpn_dialog.xml b/res/layout/vpn_dialog.xml
index ffbfd4d..b4779e6 100644
--- a/res/layout/vpn_dialog.xml
+++ b/res/layout/vpn_dialog.xml
@@ -15,7 +15,7 @@
 -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content">
     <LinearLayout android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -96,15 +96,27 @@
                         android:prompt="@string/vpn_ipsec_ca_cert" />
             </LinearLayout>
 
+            <CheckBox style="@style/vpn_value" android:id="@+id/show_options"
+                    android:singleLine="false"
+                    android:text="@string/vpn_show_options"/>
+        </LinearLayout>
+
+        <LinearLayout android:id="@+id/options"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:visibility="gone">
             <TextView style="@style/vpn_label" android:text="@string/vpn_search_domains"/>
             <EditText style="@style/vpn_value" android:id="@+id/search_domains"
                     android:hint="@string/vpn_not_used"/>
 
-            <!-- Not sure if we have time to make it. -->
-            <TextView style="@style/vpn_label" android:text="@string/vpn_routes"
-                    android:visibility="gone"/>
+            <TextView style="@style/vpn_label" android:text="@string/vpn_dns_servers"/>
+            <EditText style="@style/vpn_value" android:id="@+id/dns_servers"
+                    android:hint="@string/vpn_not_used"/>
+
+            <TextView style="@style/vpn_label" android:text="@string/vpn_routes"/>
             <EditText style="@style/vpn_value" android:id="@+id/routes"
-                    android:visibility="gone"/>
+                    android:hint="@string/vpn_not_used"/>
         </LinearLayout>
 
         <LinearLayout android:id="@+id/login"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0291fa2..6c26cbc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3503,18 +3503,21 @@
     <string name="vpn_ipsec_user_cert">IPSec user certificate</string>
     <!-- Selection label for the IPSec CA certificate of a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_ipsec_ca_cert">IPSec CA certificate</string>
+    <!-- Checkbox label to show advanced options of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_show_options">Show advanced options</string>
     <!-- Input label for the DNS search domains of a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_search_domains">DNS search domains</string>
+    <!-- Input label for the DNS servers of a VPN network. [CHAR LIMIT=40] -->
+    <string name="vpn_dns_servers">DNS servers (e.g. 8.8.8.8)</string>
     <!-- Input label for the forwarding routes of a VPN network. [CHAR LIMIT=40] -->
-    <string name="vpn_routes">Forwarding routes</string>
+    <string name="vpn_routes">Forwarding routes (e.g. 10.0.0.0/8)</string>
     <!-- Input label for the username of a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_username">Username</string>
     <!-- Input label for the password of a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_password">Password</string>
     <!-- Checkbox label to save the username and the password for a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_save_login">Save account information</string>
-
-    <!-- Hint for not filling an optional field in a VPN configuration. [CHAR LIMIT=40] -->
+    <!-- Hint for not using an optional feature of a VPN network. [CHAR LIMIT=40] -->
     <string name="vpn_not_used">(not used)</string>
     <!-- Option to not use a CA certificate to verify the VPN server. [CHAR LIMIT=40] -->
     <string name="vpn_no_ca_cert">(do not verify server)</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ffa7912..32e0a48 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -143,15 +143,14 @@
     <style name="vpn_label">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
-        <item name="android:textSize">16sp</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
     </style>
 
     <style name="vpn_value">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
-        <item name="android:textSize">18sp</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
         <item name="android:singleLine">true</item>
-        <item name="android:paddingBottom">1mm</item>
     </style>
 
     <style name="InputMethodPreferenceStyle">
diff --git a/src/com/android/settings/vpn2/VpnDialog.java b/src/com/android/settings/vpn2/VpnDialog.java
index d644700..c1a4531 100644
--- a/src/com/android/settings/vpn2/VpnDialog.java
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -27,15 +27,18 @@
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.Spinner;
 import android.widget.TextView;
 
-class VpnDialog extends AlertDialog implements TextWatcher, OnItemSelectedListener {
+import java.net.InetAddress;
+
+class VpnDialog extends AlertDialog implements TextWatcher,
+        View.OnClickListener, AdapterView.OnItemSelectedListener {
     private final KeyStore mKeyStore = KeyStore.getInstance();
     private final DialogInterface.OnClickListener mListener;
     private final VpnProfile mProfile;
@@ -50,6 +53,7 @@
     private TextView mUsername;
     private TextView mPassword;
     private TextView mSearchDomains;
+    private TextView mDnsServers;
     private TextView mRoutes;
     private CheckBox mMppe;
     private TextView mL2tpSecret;
@@ -82,6 +86,7 @@
         mUsername = (TextView) mView.findViewById(R.id.username);
         mPassword = (TextView) mView.findViewById(R.id.password);
         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
+        mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
         mRoutes = (TextView) mView.findViewById(R.id.routes);
         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
@@ -98,6 +103,7 @@
         mUsername.setText(mProfile.username);
         mPassword.setText(mProfile.password);
         mSearchDomains.setText(mProfile.searchDomains);
+        mDnsServers.setText(mProfile.dnsServers);
         mRoutes.setText(mProfile.routes);
         mMppe.setChecked(mProfile.mppe);
         mL2tpSecret.setText(mProfile.l2tpSecret);
@@ -115,6 +121,8 @@
         mServer.addTextChangedListener(this);
         mUsername.addTextChangedListener(this);
         mPassword.addTextChangedListener(this);
+        mDnsServers.addTextChangedListener(this);
+        mRoutes.addTextChangedListener(this);
         mIpsecSecret.addTextChangedListener(this);
         mIpsecUserCert.setOnItemSelectedListener(this);
 
@@ -131,6 +139,15 @@
             // Show type-specific fields.
             changeType(mProfile.type);
 
+            // Show advanced options directly if any of them is set.
+            View showOptions = mView.findViewById(R.id.show_options);
+            if (mProfile.searchDomains.isEmpty() && mProfile.dnsServers.isEmpty() &&
+                    mProfile.routes.isEmpty()) {
+                showOptions.setOnClickListener(this);
+            } else {
+                onClick(showOptions);
+            }
+
             // Create a button to save the profile.
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(R.string.vpn_save), mListener);
@@ -155,6 +172,10 @@
         // Disable the action button if necessary.
         getButton(DialogInterface.BUTTON_POSITIVE)
                 .setEnabled(mEditing ? valid : validate(false));
+
+        // Workaround to resize the dialog for the input method.
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
     }
 
     @Override
@@ -171,6 +192,12 @@
     }
 
     @Override
+    public void onClick(View showOptions) {
+        showOptions.setVisibility(View.GONE);
+        mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
+    }
+
+    @Override
     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
         if (parent == mType) {
             changeType(position);
@@ -221,7 +248,9 @@
         if (!editing) {
             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
         }
-        if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
+        if (mName.getText().length() == 0 || mServer.getText().length() == 0 ||
+                !validateAddresses(mDnsServers.getText().toString(), false) ||
+                !validateAddresses(mRoutes.getText().toString(), true)) {
             return false;
         }
         switch (mType.getSelectedItemPosition()) {
@@ -239,6 +268,33 @@
         return false;
     }
 
+    private boolean validateAddresses(String addresses, boolean cidr) {
+        try {
+            for (String address : addresses.split(" ")) {
+                if (address.isEmpty()) {
+                    continue;
+                }
+                // Legacy VPN currently only supports IPv4.
+                int prefixLength = 32;
+                if (cidr) {
+                    String[] parts = address.split("/", 2);
+                    address = parts[0];
+                    prefixLength = Integer.parseInt(parts[1]);
+                }
+                byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
+                int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
+                        (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
+                if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
+                        (prefixLength < 32 && (integer << prefixLength) != 0)) {
+                    return false;
+                }
+            }
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
     private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
         Context context = getContext();
         String first = (firstId == 0) ? "" : context.getString(firstId);
@@ -279,6 +335,7 @@
         profile.username = mUsername.getText().toString();
         profile.password = mPassword.getText().toString();
         profile.searchDomains = mSearchDomains.getText().toString().trim();
+        profile.dnsServers = mDnsServers.getText().toString().trim();
         profile.routes = mRoutes.getText().toString().trim();
 
         // Then, save type-specific fields.
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index 4dbb6bd..2ab99e8 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -108,7 +108,7 @@
                 Credentials.getInstance().unlock(getActivity());
             } else {
                 // We already tried, but it is still not working!
-                getActivity().getFragmentManager().popBackStack();
+                finishFragment();
             }
             mUnlocking = !mUnlocking;
             return;
@@ -429,8 +429,11 @@
         config.interfaze = interfaze;
         config.session = profile.name;
         config.routes = profile.routes;
+        if (!profile.dnsServers.isEmpty()) {
+            config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
+        }
         if (!profile.searchDomains.isEmpty()) {
-            config.searchDomains = Arrays.asList(profile.searchDomains.split(" "));
+            config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
         }
 
         mService.startLegacyVpn(config, racoon, mtpd);