vpn2: show third-party VPN services

VPN apps are shown alongside configured VPNs now. The requirement that
a password is set is now only enforced when setting up a configured
VPN as this is not necessary for apps.

Some UI redesign.

Bug: 19573824
Bug: 17474682
Bug: 19575658
Change-Id: I02bd977136929647d65b9784fb4cc5df24b45428
diff --git a/src/com/android/settings/vpn2/ConfigDialogFragment.java b/src/com/android/settings/vpn2/ConfigDialogFragment.java
new file mode 100644
index 0000000..42e1614
--- /dev/null
+++ b/src/com/android/settings/vpn2/ConfigDialogFragment.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 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.settings.vpn2;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.IConnectivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.settings.R;
+
+/**
+ * Fragment wrapper around a {@link ConfigDialog}.
+ */
+public class ConfigDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+    private static final String TAG_CONFIG_DIALOG = "vpnconfigdialog";
+    private static final String TAG = "ConfigDialogFragment";
+
+    private static final String ARG_PROFILE = "profile";
+    private static final String ARG_EDITING = "editing";
+    private static final String ARG_EXISTS = "exists";
+
+    private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
+            ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+
+    private boolean mUnlocking = false;
+
+    public static void show(VpnSettings parent, VpnProfile profile, boolean edit, boolean exists) {
+        if (!parent.isAdded()) return;
+
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_PROFILE, profile);
+        args.putBoolean(ARG_EDITING, edit);
+        args.putBoolean(ARG_EXISTS, exists);
+
+        final ConfigDialogFragment frag = new ConfigDialogFragment();
+        frag.setArguments(args);
+        frag.setTargetFragment(parent, 0);
+        frag.show(parent.getFragmentManager(), TAG_CONFIG_DIALOG);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // Check KeyStore here, so others do not need to deal with it.
+        if (!KeyStore.getInstance().isUnlocked()) {
+            if (!mUnlocking) {
+                // Let us unlock KeyStore. See you later!
+                Credentials.getInstance().unlock(getActivity());
+            } else {
+                // We already tried, but it is still not working!
+                dismiss();
+            }
+            mUnlocking = !mUnlocking;
+            return;
+        }
+
+        // Now KeyStore is always unlocked. Reset the flag.
+        mUnlocking = false;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Bundle args = getArguments();
+        VpnProfile profile = (VpnProfile) args.getParcelable(ARG_PROFILE);
+        boolean editing = args.getBoolean(ARG_EDITING);
+        boolean exists = args.getBoolean(ARG_EXISTS);
+
+        return new ConfigDialog(getActivity(), this, profile, editing, exists);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialogInterface, int button) {
+        ConfigDialog dialog = (ConfigDialog) getDialog();
+        VpnProfile profile = dialog.getProfile();
+
+        if (button == DialogInterface.BUTTON_POSITIVE) {
+            // Update KeyStore entry
+            KeyStore.getInstance().put(Credentials.VPN + profile.key, profile.encode(),
+                    KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
+
+            // Flush out old version of profile
+            disconnect(profile);
+
+            // If we are not editing, connect!
+            if (!dialog.isEditing()) {
+                try {
+                    connect(profile);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to connect", e);
+                }
+            }
+        } else if (button == DialogInterface.BUTTON_NEUTRAL) {
+            // Disable profile if connected
+            disconnect(profile);
+
+            // Delete from KeyStore
+            KeyStore.getInstance().delete(Credentials.VPN + profile.key, KeyStore.UID_SELF);
+        }
+        dismiss();
+    }
+
+    @Override
+    public void dismiss() {
+        ((VpnSettings) getTargetFragment()).update();
+        super.dismiss();
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        dismiss();
+        super.onCancel(dialog);
+    }
+
+    private void connect(VpnProfile profile) throws RemoteException {
+        try {
+            mService.startLegacyVpn(profile);
+        } catch (IllegalStateException e) {
+            Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void disconnect(VpnProfile profile) {
+        try {
+            LegacyVpnInfo connected = mService.getLegacyVpnInfo();
+            if (connected != null && profile.key.equals(connected.key)) {
+                mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to disconnect", e);
+        }
+    }
+}