aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java')
-rw-r--r--app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java435
1 files changed, 435 insertions, 0 deletions
diff --git a/app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java b/app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java
new file mode 100644
index 0000000..4acf28a
--- /dev/null
+++ b/app/src/fil/libre/repwifiapp/activities/VpnAndConnectionBoundActivity.java
@@ -0,0 +1,435 @@
+//
+// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net>
+//
+// This file is part of RepWifiApp.
+// This file is based upon the example file included in
+// de.blinkt.openvpn package by Arne Schwabe.
+//
+// RepWifiApp 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 3 of the License, or
+// (at your option) any later version.
+//
+// RepWifiApp 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.
+//
+// You should have received a copy of the GNU General Public License
+// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>.
+//
+// ********************************************************************
+
+package fil.libre.repwifiapp.activities;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.widget.Toast;
+import de.blinkt.openvpn.api.APIVpnProfile;
+import de.blinkt.openvpn.api.IOpenVPNAPIService;
+import fil.libre.repwifiapp.ActivityLauncher;
+import fil.libre.repwifiapp.R;
+import fil.libre.repwifiapp.Utils;
+import fil.libre.repwifiapp.helpers.Logger;
+import fil.libre.repwifiapp.network.AccessPointInfo;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class VpnAndConnectionBoundActivity extends ConnectionBoundActivity {
+
+ public static final String SERVICE_PACKAGE_NAME = "de.blinkt.openvpn";
+ public static final String APP_COMMON_NAME = "OpenVPN for Android";
+ public static final String PLACEHOLDER_APPNAME = "[VPN_EXT_APP]";
+
+ private static final int ACTION_NONE = 0;
+ private static final int ACTION_GET_PROFILES = 1;
+ private static final int ACTION_CONNECT = 2;
+
+ protected IOpenVPNAPIService _vpnSvc;
+
+ private AccessPointInfo _lastInfo;
+ private int _lastAction = ACTION_NONE;
+
+ private int lastRequestCode = ActivityLauncher.RequestCode.NONE;
+ private boolean permissionAsked = false;
+
+ private ServiceConnection _svcConnection;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ bindVpnService(ACTION_NONE);
+ }
+
+ private void bindVpnService(int action) {
+
+ if (!isExternalAppInstalled()) {
+ Logger.logDebug("External VPN app is not installed. Skipping vpn service binding.");
+ return;
+ }
+
+ _lastAction = action;
+
+ this._svcConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ _vpnSvc = IOpenVPNAPIService.Stub.asInterface(service);
+ onVpnServiceConnected();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ _vpnSvc = null;
+ onVpnServiceDisconnected();
+ }
+ };
+
+ Intent intentGetService = new Intent(IOpenVPNAPIService.class.getName());
+ intentGetService.setPackage(SERVICE_PACKAGE_NAME);
+ if (!bindService(intentGetService, _svcConnection, Context.BIND_AUTO_CREATE)) {
+ Logger.logError("FAILED to bind to OpenVPN service!");
+ }
+
+ }
+
+ private void unbindVpnService() {
+ if (_svcConnection != null && _vpnSvc != null) {
+ unbindService(_svcConnection);
+ }
+ }
+
+ protected void onVpnServiceConnected() {
+
+ switch (_lastAction) {
+ case ACTION_NONE:
+ return;
+
+ case ACTION_GET_PROFILES:
+ beginGetExistingVpnProfiles();
+ break;
+
+ case ACTION_CONNECT:
+ beginConnectVpn(_lastInfo);
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+ protected void onVpnServiceDisconnected() {
+ }
+
+ protected void onVpnProfilesAvailable(List<String> vpnProfiles) {
+ }
+
+ protected void onVpnPermissionDenied(){
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+
+ if (requestCode != ActivityLauncher.RequestCode.VPN_PERMISSION_CONN
+ && requestCode != ActivityLauncher.RequestCode.VPN_PERMISSION_LIST) {
+ // activity result is unrelated to our code.
+ return;
+
+ } else if (resultCode != Activity.RESULT_OK) {
+ // warn user that permission is needed to manage VPN
+
+ Logger.logDebug("User rejected vpn permission.");
+
+ String msg = getString(R.string.msg_vpn_no_permission).replace(
+ VpnAndConnectionBoundActivity.PLACEHOLDER_APPNAME,
+ VpnAndConnectionBoundActivity.APP_COMMON_NAME);
+ Utils.showMessage(msg, this);
+
+ onVpnPermissionDenied();
+ return;
+ }
+
+ switch (lastRequestCode) {
+
+ case ActivityLauncher.RequestCode.VPN_PERMISSION_CONN:
+
+ endConnectVpn();
+ break;
+
+ case ActivityLauncher.RequestCode.VPN_PERMISSION_LIST:
+
+ endGetExistingVpnProfiles();
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+ protected void close() {
+ unbindVpnService();
+ }
+
+ protected boolean isExternalAppInstalled() {
+
+ try {
+
+ ApplicationInfo i;
+ i = getPackageManager().getApplicationInfo(SERVICE_PACKAGE_NAME, 0);
+ return (i != null);
+
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+
+ }
+
+ private boolean doStartVpn(String profileUuid) {
+
+ if (profileUuid == null) {
+ Logger.logError("Invoked startVpn with null uuid");
+ return false;
+ }
+
+ if (_vpnSvc == null) {
+ Logger.logError("Invoked startVpn but inner service is null.");
+ return false;
+ }
+
+ try {
+
+ _vpnSvc.startProfile(profileUuid);
+ return true;
+
+ } catch (RemoteException e) {
+ Logger.logError("Exception while starting vpn.", e);
+ return false;
+ }
+
+ }
+
+ private String getUuidFromName(String profileName) {
+ if (_vpnSvc == null) {
+ Logger.logError("Called getUuidFromName but inner service is null!");
+ return null;
+ }
+
+ try {
+ List<APIVpnProfile> list = _vpnSvc.getProfiles();
+
+ for (APIVpnProfile vp : list) {
+ if (vp.mName.equals(profileName)) {
+ return vp.mUUID;
+ }
+ }
+
+ return null;
+
+ } catch (RemoteException e) {
+ Logger.logError("Exception while retrieving profiles from vpn service.", e);
+ return null;
+ }
+ }
+
+ /***
+ *
+ * @return Returns 0 if no permission is needed,
+ * 1 if permission is needed and it was asked successfully,
+ * a negative number on error.
+ */
+ private int askPermissionIfNeeded(int requestCode) {
+
+ Logger.logDebug("Called OpenVpnManager.askPermissionIfNeeded()");
+
+ if (_vpnSvc == null) {
+ Logger.logError("Internal vpn service is null, but not supposed to be. Aborting.");
+ return -1;
+ }
+
+ Intent pi;
+
+ try {
+ pi = _vpnSvc.prepare(getPackageName());
+ } catch (RemoteException e) {
+ Logger.logError("Exception while asking for VPN permission", e);
+ Toast t = Toast.makeText(getApplicationContext(),
+ getString(R.string.msg_vpn_service_error), Toast.LENGTH_LONG);
+
+ t.show();
+
+ String msgerr = getString(R.string.msg_vpn_error_manual_open).replace(
+ PLACEHOLDER_APPNAME, APP_COMMON_NAME);
+ Utils.showMessage(msgerr, getApplicationContext());
+
+ return -1;
+
+ }
+
+ if (pi == null) {
+ // no need to ask for permission
+ Logger.logDebug("No need for vpn permission.");
+ return 0;
+
+ } else if (!permissionAsked) {
+ // launch the intent to ask permission
+ Logger.logDebug("Need to ask for vpn permission. Starting intent..");
+ permissionAsked = true;
+ startActivityForResult(pi, requestCode);
+ return 1;
+
+ } else {
+ return 1;
+
+ }
+
+ }
+
+ private String getVpnNameIfAny(AccessPointInfo i) {
+
+ if (i == null) {
+ return null;
+ }
+
+ String profname = i.getVpnProfileName();
+ if (profname == null || profname.isEmpty()) {
+ return null;
+ } else {
+ return profname;
+ }
+
+ }
+
+ protected void beginConnectVpn(AccessPointInfo info) {
+
+ if (!isExternalAppInstalled()) {
+ return;
+ }
+
+ if (getVpnNameIfAny(info) == null) {
+ Logger.logDebug("No vpn profile set. Exiting beginConnectVpn()");
+ return;
+ }
+
+ _lastInfo = info;
+
+ if (_vpnSvc == null) {
+ bindVpnService(ACTION_CONNECT);
+ return;
+ }
+
+ // make sure we have permission to use the vpn service.
+ if (askPermissionIfNeeded(ActivityLauncher.RequestCode.VPN_PERMISSION_CONN) == 0) {
+ // no need for permission
+ Logger.logDebug("Going to endConnectVpn.");
+ endConnectVpn();
+ }
+
+ }
+
+ private void endConnectVpn() {
+
+ try {
+
+ if (_lastInfo == null) {
+ Logger.logError("Called endConnectVpn, but last AccessPointInfo is null.");
+ return;
+ }
+
+ String profname = getVpnNameIfAny(_lastInfo);
+
+ // check if profile exists
+ String profUuid = getUuidFromName(profname);
+ if (profUuid == null) {
+ // warn user that selected profile doesn't exist
+ Utils.showMessage(getString(R.string.msg_vpn_wrong_profile),
+ getApplicationContext());
+ return;
+ }
+
+ if (doStartVpn(profUuid)) {
+ Toast t = Toast.makeText(getApplicationContext(),
+ getString(R.string.msg_vpn_launched), Toast.LENGTH_LONG);
+ t.show();
+ } else {
+ Utils.showMessage(getString(R.string.msg_vpn_connect_error),
+ getApplicationContext());
+ }
+
+ } catch (Exception e) {
+ Logger.logError("Exception while endConnectVpn", e);
+
+ }
+
+ }
+
+ protected void beginGetExistingVpnProfiles() {
+
+ if (!isExternalAppInstalled()) {
+ return;
+ }
+
+ if (_vpnSvc == null) {
+ bindVpnService(ACTION_GET_PROFILES);
+ return;
+ }
+
+ // we make sure we have permission to use the vpn service.
+ if (askPermissionIfNeeded(ActivityLauncher.RequestCode.VPN_PERMISSION_LIST) == 0) {
+ // no need for permission
+ endGetExistingVpnProfiles();
+ }
+
+ }
+
+ private void endGetExistingVpnProfiles() {
+
+ try {
+ List<APIVpnProfile> list = _vpnSvc.getProfiles();
+
+ List<String> ret = new ArrayList<String>();
+ for (APIVpnProfile vp : list) {
+ ret.add(vp.mName);
+ }
+
+ onVpnProfilesAvailable(ret);
+
+ } catch (RemoteException e) {
+ Logger.logError("Exception while retrieving profiles from vpn service.", e);
+ }
+
+ }
+
+
+
+ protected boolean disconnectVpn() {
+
+ if (_vpnSvc == null) {
+ Logger.logDebug("Attempted to disconnect from VPN, but inner service is null");
+ return true;
+ }
+
+ try {
+ _vpnSvc.disconnect();
+ return true;
+ } catch (Exception e) {
+ Logger.logError("Exception while disconnecting from vpn.", e);
+ return false;
+ }
+
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ this.close();
+ }
+
+}