<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.VpnProfileControlActivity"
+ android:theme="@style/TransparentActivity"
+ android:taskAffinity=""
+ android:excludeFromRecents="true"
+ android:launchMode="singleTask" >
<intent-filter>
<action android:name="org.strongswan.android.action.START_PROFILE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter>
+ <action android:name="org.strongswan.android.action.DISCONNECT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<activity
android:name=".ui.VpnProfileDetailActivity" >
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
import org.strongswan.android.ui.MainActivity;
+import org.strongswan.android.ui.VpnProfileControlActivity;
import org.strongswan.android.utils.IPRange;
import org.strongswan.android.utils.IPRangeSet;
import org.strongswan.android.utils.SettingsWriter;
{
if (add_action)
{
- Intent intent = new Intent(getApplicationContext(), MainActivity.class);
- intent.setAction(MainActivity.DISCONNECT);
+ Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
+ intent.setAction(VpnProfileControlActivity.DISCONNECT);
PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(R.drawable.ic_notification_disconnect, getString(R.string.disconnect), pending);
/*
- * Copyright (C) 2012-2017 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
package org.strongswan.android.ui;
import android.app.Dialog;
-import android.app.Service;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.VpnService;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDialogFragment;
import android.text.format.Formatter;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.EditText;
import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
-import org.strongswan.android.data.VpnProfileDataSource;
-import org.strongswan.android.data.VpnType.VpnTypeFeature;
-import org.strongswan.android.logic.CharonVpnService;
import org.strongswan.android.logic.TrustedCertificateManager;
-import org.strongswan.android.logic.VpnStateService;
-import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
-import java.util.UUID;
public class MainActivity extends AppCompatActivity implements OnVpnProfileSelectedListener
{
public static final String CONTACT_EMAIL = "android@strongswan.org";
- public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
- public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
- public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
public static final String EXTRA_CRL_LIST = "org.strongswan.android.CRL_LIST";
+
/**
* Use "bring your own device" (BYOD) features
*/
public static final boolean USE_BYOD = true;
- private static final int PREPARE_VPN_SERVICE = 0;
- private static final String PROFILE_NAME = "org.strongswan.android.MainActivity.PROFILE_NAME";
- private static final String PROFILE_REQUIRES_PASSWORD = "org.strongswan.android.MainActivity.REQUIRES_PASSWORD";
- private static final String PROFILE_RECONNECT = "org.strongswan.android.MainActivity.RECONNECT";
- private static final String PROFILE_DISCONNECT = "org.strongswan.android.MainActivity.DISCONNECT";
- private static final String PROFILE_FOREGROUND = "org.strongswan.android.MainActivity.PROFILE_FOREGROUND";
- private static final String DIALOG_TAG = "Dialog";
-
- private boolean mIsVisible;
- private Bundle mProfileInfo;
- private VpnStateService mService;
- private final ServiceConnection mServiceConnection = new ServiceConnection()
- {
- @Override
- public void onServiceDisconnected(ComponentName name)
- {
- mService = null;
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service)
- {
- mService = ((VpnStateService.LocalBinder)service).getService();
- if (START_PROFILE.equals(getIntent().getAction()))
- {
- startVpnProfile(getIntent(), false);
- }
- else if (DISCONNECT.equals(getIntent().getAction()))
- {
- disconnect(false);
- }
- }
- };
+ private static final String DIALOG_TAG = "Dialog";
@Override
public void onCreate(Bundle savedInstanceState)
bar.setDisplayShowTitleEnabled(false);
bar.setIcon(R.drawable.ic_launcher);
- this.bindService(new Intent(this, VpnStateService.class),
- mServiceConnection, Service.BIND_AUTO_CREATE);
-
/* load CA certificates in a background task */
new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- @Override
- protected void onDestroy()
- {
- super.onDestroy();
- if (mService != null)
- {
- this.unbindService(mServiceConnection);
- }
- }
-
- @Override
- protected void onStart()
- {
- super.onStart();
- mIsVisible = true;
- }
-
- @Override
- protected void onStop()
- {
- super.onStop();
- mIsVisible = false;
- }
-
- /**
- * Due to launchMode=singleTop this is called if the Activity already exists
- */
- @Override
- protected void onNewIntent(Intent intent)
- {
- super.onNewIntent(intent);
-
- if (START_PROFILE.equals(intent.getAction()))
- {
- startVpnProfile(intent, mIsVisible);
- }
- else if (DISCONNECT.equals(intent.getAction()))
- {
- disconnect(mIsVisible);
- }
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
}
}
- /**
- * Prepare the VpnService. If this succeeds the current VPN profile is
- * started.
- *
- * @param profileInfo a bundle containing the information about the profile to be started
- */
- protected void prepareVpnService(Bundle profileInfo)
- {
- Intent intent;
- try
- {
- intent = VpnService.prepare(this);
- }
- catch (IllegalStateException ex)
- {
- /* this happens if the always-on VPN feature (Android 4.2+) is activated */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
- return;
- }
- catch (NullPointerException ex)
- {
- /* not sure when this happens exactly, but apparently it does */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
- return;
- }
- /* store profile info until the user grants us permission */
- mProfileInfo = profileInfo;
- if (intent != null)
- {
- try
- {
- startActivityForResult(intent, PREPARE_VPN_SERVICE);
- }
- catch (ActivityNotFoundException ex)
- {
- /* it seems some devices, even though they come with Android 4,
- * don't have the VPN components built into the system image.
- * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
- * will not be found then */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
- }
- }
- else
- { /* user already granted permission to use VpnService */
- onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data)
- {
- switch (requestCode)
- {
- case PREPARE_VPN_SERVICE:
- if (resultCode == RESULT_OK && mProfileInfo != null)
- {
- Intent intent = new Intent(this, CharonVpnService.class);
- intent.putExtras(mProfileInfo);
- this.startService(intent);
- }
- else
- { /* this happens if the always-on VPN feature is activated by a different app or the user declined */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
- }
- break;
- default:
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
@Override
public void onVpnProfileSelected(VpnProfile profile)
{
- startVpnProfile(profile, true);
- }
-
- /**
- * Start the given VPN profile
- *
- * @param profile VPN profile
- * @param foreground whether this was initiated when the activity was visible
- */
- public void startVpnProfile(VpnProfile profile, boolean foreground)
- {
- Bundle profileInfo = new Bundle();
- profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
- profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
- profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
- profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
- profileInfo.putString(PROFILE_NAME, profile.getName());
-
- removeFragmentByTag(DIALOG_TAG);
-
- if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
- {
- profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId());
- profileInfo.putBoolean(PROFILE_FOREGROUND, foreground);
-
- ConfirmationDialog dialog = new ConfirmationDialog();
- dialog.setArguments(profileInfo);
- dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
- return;
- }
- startVpnProfile(profileInfo);
- }
-
- /**
- * Start the given VPN profile asking the user for a password if required.
- *
- * @param profileInfo data about the profile
- */
- private void startVpnProfile(Bundle profileInfo)
- {
- if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
- profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
- {
- LoginDialog login = new LoginDialog();
- login.setArguments(profileInfo);
- login.show(getSupportFragmentManager(), DIALOG_TAG);
- return;
- }
- prepareVpnService(profileInfo);
- }
-
- /**
- * Start the VPN profile referred to by the given intent. Displays an error
- * if the profile doesn't exist.
- *
- * @param intent Intent that caused us to start this
- * @param foreground whether this was initiated when the activity was visible
- */
- private void startVpnProfile(Intent intent, boolean foreground)
- {
- VpnProfile profile = null;
-
- VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
- dataSource.open();
- String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
- if (profileUUID != null)
- {
- try
- {
- profile = dataSource.getVpnProfile(UUID.fromString(profileUUID));
- }
- catch (Exception e)
- { /* invalid UUID */
- e.printStackTrace();
- }
- }
- else
- {
- long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
- if (profileId > 0)
- {
- profile = dataSource.getVpnProfile(profileId);
- }
- }
- dataSource.close();
-
- if (profile != null)
- {
- startVpnProfile(profile, foreground);
- }
- else
- {
- Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Disconnect the current connection, if any (silently ignored if there is no connection).
- */
- private void disconnect(boolean foreground)
- {
- removeFragmentByTag(DIALOG_TAG);
-
- if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
- {
- Bundle args = new Bundle();
- args.putBoolean(PROFILE_DISCONNECT, true);
- args.putBoolean(PROFILE_FOREGROUND, foreground);
-
- ConfirmationDialog dialog = new ConfirmationDialog();
- dialog.setArguments(args);
- dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
- }
+ Intent intent = new Intent(this, VpnProfileControlActivity.class);
+ intent.setAction(VpnProfileControlActivity.START_PROFILE);
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ startActivity(intent);
}
/**
}
}
- /**
- * Class that displays a confirmation dialog if a VPN profile is already connected
- * and then initiates the selected VPN profile if the user confirms the dialog.
- */
- public static class ConfirmationDialog extends AppCompatDialogFragment
- {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle profileInfo = getArguments();
- int icon = android.R.drawable.ic_dialog_alert;
- int title = R.string.connect_profile_question;
- int message = R.string.replaces_active_connection;
- int button = R.string.connect;
-
- if (profileInfo.getBoolean(PROFILE_RECONNECT))
- {
- icon = android.R.drawable.ic_dialog_info;
- title = R.string.vpn_connected;
- message = R.string.vpn_profile_connected;
- button = R.string.reconnect;
- }
- else if (profileInfo.getBoolean(PROFILE_DISCONNECT))
- {
- title = R.string.disconnect_question;
- message = R.string.disconnect_active_connection;
- button = R.string.disconnect;
- }
-
- DialogInterface.OnClickListener connectListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- MainActivity activity = (MainActivity)getActivity();
- activity.startVpnProfile(profileInfo);
- }
- };
- DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- MainActivity activity = (MainActivity)getActivity();
- if (activity.mService != null)
- {
- activity.mService.disconnect();
- }
- }
- };
- DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dismiss();
- if (!profileInfo.getBoolean(PROFILE_FOREGROUND))
- { /* if the app was not in the foreground before this action was triggered
- * externally, we just close the activity if canceled */
- getActivity().finish();
- }
- }
- };
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setIcon(icon)
- .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME)))
- .setMessage(message);
-
- if (profileInfo.getBoolean(PROFILE_DISCONNECT))
- {
- builder.setPositiveButton(button, disconnectListener);
- }
- else
- {
- builder.setPositiveButton(button, connectListener);
- }
-
- if (profileInfo.getBoolean(PROFILE_RECONNECT))
- {
- builder.setNegativeButton(R.string.disconnect, disconnectListener);
- builder.setNeutralButton(android.R.string.cancel, cancelListener);
- }
- else
- {
- builder.setNegativeButton(android.R.string.cancel, cancelListener);
- }
- return builder.create();
- }
- }
-
- /**
- * Class that displays a login dialog and initiates the selected VPN
- * profile if the user confirms the dialog.
- */
- public static class LoginDialog extends AppCompatDialogFragment
- {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle profileInfo = getArguments();
- LayoutInflater inflater = getActivity().getLayoutInflater();
- View view = inflater.inflate(R.layout.login_dialog, null);
- EditText username = (EditText)view.findViewById(R.id.username);
- username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
- final EditText password = (EditText)view.findViewById(R.id.password);
-
- AlertDialog.Builder adb = new AlertDialog.Builder(getActivity());
- adb.setView(view);
- adb.setTitle(getString(R.string.login_title));
- adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int whichButton)
- {
- MainActivity activity = (MainActivity)getActivity();
- profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
- activity.prepareVpnService(profileInfo);
- }
- });
- adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dismiss();
- }
- });
- return adb.create();
- }
- }
-
- /**
- * Class representing an error message which is displayed if VpnService is
- * not supported on the current device.
- */
- public static class VpnNotSupportedError extends AppCompatDialogFragment
- {
- static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
-
- public static void showWithMessage(AppCompatActivity activity, int messageId)
- {
- Bundle bundle = new Bundle();
- bundle.putInt(ERROR_MESSAGE_ID, messageId);
- VpnNotSupportedError dialog = new VpnNotSupportedError();
- dialog.setArguments(bundle);
- dialog.show(activity.getSupportFragmentManager(), DIALOG_TAG);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle arguments = getArguments();
- final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
- return new AlertDialog.Builder(getActivity())
- .setTitle(R.string.vpn_not_supported_title)
- .setMessage(messageId)
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int id)
- {
- dialog.dismiss();
- }
- }).create();
- }
- }
-
/**
* Confirmation dialog to clear CRL cache
*/
--- /dev/null
+/*
+ * Copyright (C) 2012-2018 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.ui;
+
+import android.app.Dialog;
+import android.app.Service;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.VpnService;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType.VpnTypeFeature;
+import org.strongswan.android.logic.CharonVpnService;
+import org.strongswan.android.logic.VpnStateService;
+import org.strongswan.android.logic.VpnStateService.State;
+
+import java.util.UUID;
+
+public class VpnProfileControlActivity extends AppCompatActivity
+{
+ public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
+ public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
+ public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
+
+ private static final int PREPARE_VPN_SERVICE = 0;
+ private static final String PROFILE_NAME = "PROFILE_NAME";
+ private static final String PROFILE_REQUIRES_PASSWORD = "REQUIRES_PASSWORD";
+ private static final String PROFILE_RECONNECT = "RECONNECT";
+ private static final String PROFILE_DISCONNECT = "DISCONNECT";
+ private static final String DIALOG_TAG = "Dialog";
+
+ private Bundle mProfileInfo;
+ private VpnStateService mService;
+ private final ServiceConnection mServiceConnection = new ServiceConnection()
+ {
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ mService = ((VpnStateService.LocalBinder)service).getService();
+
+ if (START_PROFILE.equals(getIntent().getAction()))
+ {
+ startVpnProfile(getIntent());
+ }
+ else if (DISCONNECT.equals(getIntent().getAction()))
+ {
+ disconnect();
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ this.bindService(new Intent(this, VpnStateService.class),
+ mServiceConnection, Service.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ if (mService != null)
+ {
+ this.unbindService(mServiceConnection);
+ }
+ }
+
+ /**
+ * Due to launchMode=singleTop this is called if the Activity already exists
+ */
+ @Override
+ protected void onNewIntent(Intent intent)
+ {
+ super.onNewIntent(intent);
+
+ if (START_PROFILE.equals(intent.getAction()))
+ {
+ startVpnProfile(intent);
+ }
+ else if (DISCONNECT.equals(intent.getAction()))
+ {
+ disconnect();
+ }
+ }
+
+ /**
+ * Prepare the VpnService. If this succeeds the current VPN profile is
+ * started.
+ *
+ * @param profileInfo a bundle containing the information about the profile to be started
+ */
+ protected void prepareVpnService(Bundle profileInfo)
+ {
+ Intent intent;
+ try
+ {
+ intent = VpnService.prepare(this);
+ }
+ catch (IllegalStateException ex)
+ {
+ /* this happens if the always-on VPN feature (Android 4.2+) is activated */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
+ return;
+ }
+ catch (NullPointerException ex)
+ {
+ /* not sure when this happens exactly, but apparently it does */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
+ return;
+ }
+ /* store profile info until the user grants us permission */
+ mProfileInfo = profileInfo;
+ if (intent != null)
+ {
+ try
+ {
+ startActivityForResult(intent, PREPARE_VPN_SERVICE);
+ }
+ catch (ActivityNotFoundException ex)
+ {
+ /* it seems some devices, even though they come with Android 4,
+ * don't have the VPN components built into the system image.
+ * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
+ * will not be found then */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
+ }
+ }
+ else
+ { /* user already granted permission to use VpnService */
+ onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ switch (requestCode)
+ {
+ case PREPARE_VPN_SERVICE:
+ if (resultCode == RESULT_OK && mProfileInfo != null)
+ {
+ Intent intent = new Intent(this, CharonVpnService.class);
+ intent.putExtras(mProfileInfo);
+ this.startService(intent);
+ finish();
+ }
+ else
+ { /* this happens if the always-on VPN feature is activated by a different app or the user declined */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
+ }
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ /**
+ * Start the given VPN profile
+ *
+ * @param profile VPN profile
+ */
+ public void startVpnProfile(VpnProfile profile)
+ {
+ Bundle profileInfo = new Bundle();
+ profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
+ profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
+ profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
+ profileInfo.putString(PROFILE_NAME, profile.getName());
+
+ removeFragmentByTag(DIALOG_TAG);
+
+ if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
+ {
+ profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId());
+
+ ConfirmationDialog dialog = new ConfirmationDialog();
+ dialog.setArguments(profileInfo);
+ dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
+ return;
+ }
+ startVpnProfile(profileInfo);
+ }
+
+ /**
+ * Start the given VPN profile asking the user for a password if required.
+ *
+ * @param profileInfo data about the profile
+ */
+ private void startVpnProfile(Bundle profileInfo)
+ {
+ if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
+ profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
+ {
+ LoginDialog login = new LoginDialog();
+ login.setArguments(profileInfo);
+ login.show(getSupportFragmentManager(), DIALOG_TAG);
+ return;
+ }
+ prepareVpnService(profileInfo);
+ }
+
+ /**
+ * Start the VPN profile referred to by the given intent. Displays an error
+ * if the profile doesn't exist.
+ *
+ * @param intent Intent that caused us to start this
+ */
+ private void startVpnProfile(Intent intent)
+ {
+ VpnProfile profile = null;
+
+ VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
+ dataSource.open();
+ String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
+ if (profileUUID != null)
+ {
+ try
+ {
+ profile = dataSource.getVpnProfile(UUID.fromString(profileUUID));
+ }
+ catch (Exception e)
+ { /* invalid UUID */
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
+ if (profileId > 0)
+ {
+ profile = dataSource.getVpnProfile(profileId);
+ }
+ }
+ dataSource.close();
+
+ if (profile != null)
+ {
+ startVpnProfile(profile);
+ }
+ else
+ {
+ Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ /**
+ * Disconnect the current connection, if any (silently ignored if there is no connection).
+ */
+ private void disconnect()
+ {
+ removeFragmentByTag(DIALOG_TAG);
+
+ if (mService != null)
+ {
+ if (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING)
+ {
+ Bundle args = new Bundle();
+ args.putBoolean(PROFILE_DISCONNECT, true);
+
+ ConfirmationDialog dialog = new ConfirmationDialog();
+ dialog.setArguments(args);
+ dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
+ }
+ else
+ {
+ finish();
+ }
+ }
+ }
+
+ /**
+ * Dismiss dialog if shown
+ */
+ public void removeFragmentByTag(String tag)
+ {
+ FragmentManager fm = getSupportFragmentManager();
+ Fragment login = fm.findFragmentByTag(tag);
+ if (login != null)
+ {
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.remove(login);
+ ft.commit();
+ }
+ }
+
+ /**
+ * Class that displays a confirmation dialog if a VPN profile is already connected
+ * and then initiates the selected VPN profile if the user confirms the dialog.
+ */
+ public static class ConfirmationDialog extends AppCompatDialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle profileInfo = getArguments();
+ int icon = android.R.drawable.ic_dialog_alert;
+ int title = R.string.connect_profile_question;
+ int message = R.string.replaces_active_connection;
+ int button = R.string.connect;
+
+ if (profileInfo.getBoolean(PROFILE_RECONNECT))
+ {
+ icon = android.R.drawable.ic_dialog_info;
+ title = R.string.vpn_connected;
+ message = R.string.vpn_profile_connected;
+ button = R.string.reconnect;
+ }
+ else if (profileInfo.getBoolean(PROFILE_DISCONNECT))
+ {
+ title = R.string.disconnect_question;
+ message = R.string.disconnect_active_connection;
+ button = R.string.disconnect;
+ }
+
+ DialogInterface.OnClickListener connectListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ activity.startVpnProfile(profileInfo);
+ }
+ };
+ DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ if (activity.mService != null)
+ {
+ activity.mService.disconnect();
+ }
+ getActivity().finish();
+ }
+ };
+ DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ getActivity().finish();
+ }
+ };
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setIcon(icon)
+ .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME)))
+ .setMessage(message);
+
+ if (profileInfo.getBoolean(PROFILE_DISCONNECT))
+ {
+ builder.setPositiveButton(button, disconnectListener);
+ }
+ else
+ {
+ builder.setPositiveButton(button, connectListener);
+ }
+
+ if (profileInfo.getBoolean(PROFILE_RECONNECT))
+ {
+ builder.setNegativeButton(R.string.disconnect, disconnectListener);
+ builder.setNeutralButton(android.R.string.cancel, cancelListener);
+ }
+ else
+ {
+ builder.setNegativeButton(android.R.string.cancel, cancelListener);
+ }
+ return builder.create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+
+ /**
+ * Class that displays a login dialog and initiates the selected VPN
+ * profile if the user confirms the dialog.
+ */
+ public static class LoginDialog extends AppCompatDialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle profileInfo = getArguments();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.login_dialog, null);
+ EditText username = (EditText)view.findViewById(R.id.username);
+ username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
+ final EditText password = (EditText)view.findViewById(R.id.password);
+
+ AlertDialog.Builder adb = new AlertDialog.Builder(getActivity());
+ adb.setView(view);
+ adb.setTitle(getString(R.string.login_title));
+ adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
+ activity.prepareVpnService(profileInfo);
+ }
+ });
+ adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ getActivity().finish();
+ }
+ });
+ return adb.create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+
+ /**
+ * Class representing an error message which is displayed if VpnService is
+ * not supported on the current device.
+ */
+ public static class VpnNotSupportedError extends AppCompatDialogFragment
+ {
+ static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
+
+ public static void showWithMessage(AppCompatActivity activity, int messageId)
+ {
+ Bundle bundle = new Bundle();
+ bundle.putInt(ERROR_MESSAGE_ID, messageId);
+ VpnNotSupportedError dialog = new VpnNotSupportedError();
+ dialog.setArguments(bundle);
+ dialog.show(activity.getSupportFragmentManager(), DIALOG_TAG);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle arguments = getArguments();
+ final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.vpn_not_supported_title)
+ .setMessage(messageId)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int id)
+ {
+ getActivity().finish();
+ }
+ }).create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+}
@Override
public void onVpnProfileSelected(VpnProfile profile)
{
- Intent shortcut = new Intent(MainActivity.START_PROFILE);
- shortcut.putExtra(MainActivity.EXTRA_VPN_PROFILE_ID, profile.getId());
+ Intent shortcut = new Intent(VpnProfileControlActivity.START_PROFILE);
+ shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);