From 389dfcdfad555feb1cf212ef9b42626633d5eade Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Sun, 9 Jun 2013 04:31:27 -0600 Subject: Better control and UI feedback for VPN --- src/se/leap/leapclient/ConfigHelper.java | 4 +- src/se/leap/leapclient/Dashboard.java | 138 +++++++++++++++++++++++++++++- src/se/leap/leapclient/EIP.java | 140 ++++++++++++++++++++++++++++++- src/se/leap/openvpn/LaunchVPN.java | 20 ++++- src/se/leap/openvpn/OpenVpnService.java | 15 +++- 5 files changed, 308 insertions(+), 9 deletions(-) (limited to 'src/se/leap') diff --git a/src/se/leap/leapclient/ConfigHelper.java b/src/se/leap/leapclient/ConfigHelper.java index 1c2a482a..cab5fdee 100644 --- a/src/se/leap/leapclient/ConfigHelper.java +++ b/src/se/leap/leapclient/ConfigHelper.java @@ -73,7 +73,9 @@ public class ConfigHelper { PASSWORD_KEY = "password", ALLOW_REGISTRATION_KEY = "allow_registration", EIP_SERVICE_API_PATH = "config/eip-service.json", - ERRORS_KEY = "errors" + ERRORS_KEY = "errors", + RECEIVER_TAG = "receiverTag", + REQUEST_TAG = "requestTag"; ; final public static String NG_1024 = diff --git a/src/se/leap/leapclient/Dashboard.java b/src/se/leap/leapclient/Dashboard.java index 31719d90..f10966b2 100644 --- a/src/se/leap/leapclient/Dashboard.java +++ b/src/se/leap/leapclient/Dashboard.java @@ -8,6 +8,8 @@ import org.json.JSONObject; import se.leap.leapclient.ProviderAPIResultReceiver.Receiver; import se.leap.openvpn.AboutFragment; import se.leap.openvpn.MainActivity; +import se.leap.openvpn.OpenVPN; +import se.leap.openvpn.OpenVPN.StateListener; import android.app.Activity; import android.app.AlertDialog; import android.app.DialogFragment; @@ -19,6 +21,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; +import android.os.ResultReceiver; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -30,7 +33,7 @@ import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; -public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface, Receiver { +public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface,Receiver,StateListener { protected static final int CONFIGURE_LEAP = 0; @@ -40,8 +43,14 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf private TextView providerNameTV; private TextView eipTypeTV; + private Switch eipSwitch; + private View eipDetail; + private TextView eipStatus; + + private boolean mEipWait = false; public ProviderAPIResultReceiver providerAPI_result_receiver; + private EIPReceiver mEIPReceiver; @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,6 +71,24 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf else startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); } + + @Override + protected void onStop() { + super.onStop(); + // TODO null provider should only happen before ConfigurationWizard has run, once...better way? + if (provider != null) + if (provider.hasEIP() && provider.getEIPType() == "OpenVPN") + OpenVPN.removeStateListener(this); + } + + @Override + protected void onResume() { + super.onResume(); + // TODO null provider should only happen before ConfigurationWizard has run, once...better way? + if (provider != null) + if (provider.hasEIP() && provider.getEIPType() == "OpenVPN") + OpenVPN.addStateListener(this); + } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ @@ -111,36 +138,55 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf if ( provider.hasEIP() /*&& provider.getEIP() != null*/){ // FIXME let's schedule this, so we're not doing it when we load the app startService( new Intent(EIP.ACTION_UPDATE_EIP_SERVICE) ); + if (provider.getEIPType() == "OpenVPN") + OpenVPN.addStateListener(this); serviceItemEIP(); } } private void serviceItemEIP() { + mEIPReceiver = new EIPReceiver(new Handler()); + mEIPReceiver.setReceiver(this); + + Intent intent = new Intent(this,EIP.class); + intent.setAction(EIP.ACTION_IS_EIP_RUNNING); + intent.putExtra(ConfigHelper.RECEIVER_TAG, mEIPReceiver); + startService(intent); + ((ViewStub) findViewById(R.id.eipOverviewStub)).inflate(); // Set our EIP type title eipTypeTV = (TextView) findViewById(R.id.eipType); eipTypeTV.setText(provider.getEIPType()); + // Show our EIP detail - View eipDetail = ((RelativeLayout) findViewById(R.id.eipDetail)); + eipDetail = ((RelativeLayout) findViewById(R.id.eipDetail)); View eipSettings = findViewById(R.id.eipSettings); eipSettings.setVisibility(View.GONE); // FIXME too! eipDetail.setVisibility(View.VISIBLE); + eipStatus = (TextView) findViewById(R.id.eipStatus); // TODO Bind our switch to run our EIP // What happens when our VPN stops running? does it call the listener? - Switch eipSwitch = (Switch) findViewById(R.id.eipSwitch); + eipSwitch = (Switch) findViewById(R.id.eipSwitch); eipSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (!mEipWait){ + // We're gonna have to have some patience! + buttonView.setClickable(false); + mEipWait = true; + Intent vpnIntent; if (isChecked){ vpnIntent = new Intent(EIP.ACTION_START_EIP); } else { vpnIntent = new Intent(EIP.ACTION_STOP_EIP); } + vpnIntent.putExtra(ConfigHelper.RECEIVER_TAG, mEIPReceiver); startService(vpnIntent); } + } }); //TODO write our info into the view fragment that will expand with details and a settings button @@ -333,4 +379,90 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf public static Context getAppContext() { return app; } + + @Override + public void updateState(final String state, final String logmessage, final int localizedResId) { + // Note: "states" are not organized anywhere...collected state strings: + // NOPROCESS,NONETWORK,BYTECOUNT,AUTH_FAILED + some parsing thing ( WAIT(?),AUTH,GET_CONFIG,ASSIGN_IP,CONNECTED(?) ) + // TODO follow-back calls to updateState to find set variable values passed as first param & third param (find those strings...are they all R.string.STATE_* ?) + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (eipStatus != null) { + String prefix = getString(localizedResId) + ":"; + if (state.equals("BYTECOUNT") || state.equals("NOPROCESS")) + prefix = ""; + eipStatus.setText(prefix + logmessage); + } + } + }); + } + + protected class EIPReceiver extends ResultReceiver { + + Dashboard mDashboard; + + protected EIPReceiver(Handler handler){ + super(handler); + } + + public void setReceiver(Dashboard receiver) { + mDashboard = receiver; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + + // What were we asking for, again? + String request = resultData.getString(ConfigHelper.REQUEST_TAG); + // Should the EIP switch be on? + mEipWait = true; + boolean checked = false; + + if (request == EIP.ACTION_IS_EIP_RUNNING) { + switch (resultCode){ + case RESULT_OK: + checked = true; + break; + case RESULT_CANCELED: + checked = false; + break; + } + } else if (request == EIP.ACTION_START_EIP) { + switch (resultCode){ + case RESULT_OK: + checked = true; + break; + case RESULT_CANCELED: + checked = false; + break; + } + } else if (request == EIP.ACTION_STOP_EIP) { + switch (resultCode){ + case RESULT_OK: + checked = false; + break; + case RESULT_CANCELED: + checked = true; + break; + } + } else if (request == EIP.EIP_NOTIFICATION) { + switch (resultCode){ + case RESULT_OK: + checked = true; + break; + case RESULT_CANCELED: + checked = false; + break; + } + } + + Switch eipS = ((Switch) mDashboard.findViewById(R.id.eipSwitch)); + eipS.setChecked(checked); + eipS.setClickable(true); + mEipWait = false; + } + } } diff --git a/src/se/leap/leapclient/EIP.java b/src/se/leap/leapclient/EIP.java index 21cbdfd5..867805bd 100644 --- a/src/se/leap/leapclient/EIP.java +++ b/src/se/leap/leapclient/EIP.java @@ -13,12 +13,21 @@ import org.json.JSONObject; import se.leap.openvpn.ConfigParser; import se.leap.openvpn.ConfigParser.ConfigParseError; import se.leap.openvpn.LaunchVPN; -import se.leap.openvpn.OpenVpnManagementThread; +import se.leap.openvpn.OpenVpnService; +import se.leap.openvpn.OpenVpnService.LocalBinder; import se.leap.openvpn.ProfileManager; import se.leap.openvpn.VpnProfile; + +import android.app.Activity; import android.app.IntentService; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ResultReceiver; import android.util.Log; /** @@ -31,7 +40,17 @@ public final class EIP extends IntentService { public final static String ACTION_STOP_EIP = "se.leap.leapclient.STOP_EIP"; public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.leapclient.UPDATE_EIP_SERVICE"; + public final static String ACTION_IS_EIP_RUNNING = "se.leap.leapclient.IS_RUNNING"; + + public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION"; + private static Context context; + private static ResultReceiver mReceiver; + // Binder to OpenVpnService for comm ops + private static OpenVpnService mVpnService; + private static boolean mBound = false; + // Used to store actions to "resume" onServiceConnection + private static String mPending = null; // Represents our Provider's eip-service.json private static JSONObject eipDefinition = null; @@ -56,13 +75,27 @@ public final class EIP extends IntentService { // TODO Auto-generated catch block e.printStackTrace(); } + + this.retreiveVpnService(); + } + + @Override + public void onDestroy() { + unbindService(mVpnServiceConn); + mBound = false; + + super.onDestroy(); } @Override protected void onHandleIntent(Intent intent) { // Get our action from the Intent String action = intent.getAction(); + // Get the ResultReceiver, if any + mReceiver = intent.getParcelableExtra(ConfigHelper.RECEIVER_TAG); + if ( action == ACTION_IS_EIP_RUNNING ) + this.isRunning(); if ( action == ACTION_UPDATE_EIP_SERVICE ) this.updateEIPService(); else if ( action == ACTION_START_EIP ) @@ -70,6 +103,70 @@ public final class EIP extends IntentService { else if ( action == ACTION_STOP_EIP ) this.stopEIP(); } + + private void retreiveVpnService() { + Intent bindIntent = new Intent(this,OpenVpnService.class); + bindIntent.setAction(OpenVpnService.RETRIEVE_SERVICE); + bindService(bindIntent, mVpnServiceConn, 0); + } + + private static ServiceConnection mVpnServiceConn = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LocalBinder binder = (LocalBinder) service; + mVpnService = binder.getService(); + mBound = true; + + if (mReceiver != null && mPending != null) { + + boolean running = mVpnService.isRunning(); + int resultCode = Activity.RESULT_CANCELED; + + if (mPending.equals(ACTION_IS_EIP_RUNNING)) + resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + if (mPending.equals(ACTION_START_EIP)) + resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + else if (mPending.equals(ACTION_STOP_EIP)) + resultCode = (running) ? Activity.RESULT_CANCELED + : Activity.RESULT_OK; + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, EIP_NOTIFICATION); + mReceiver.send(resultCode, resultData); + + mPending = null; + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + // XXX tell mReceiver!! + mBound = false; + + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, EIP_NOTIFICATION); + mReceiver.send(Activity.RESULT_CANCELED, resultData); + } + } + + }; + + private void isRunning() { + // TODO I don't like that whatever requested this never receives a response + // if OpenVpnService has not been START_SERVICE, though the one place this is used that's okay + if (mBound) { + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_IS_EIP_RUNNING); + int resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + mReceiver.send(resultCode, resultData); + } + } else { + mPending = ACTION_IS_EIP_RUNNING; + this.retreiveVpnService(); + } + } private void startEIP() { if (activeGateway==null) @@ -80,11 +177,50 @@ public final class EIP extends IntentService { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() ); intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() ); + intent.putExtra(ConfigHelper.RECEIVER_TAG, mReceiver); startActivity(intent); + // Let's give it 2s to get rolling... TODO there really should be a better way to do this, or it's not needed. + // Read more code in .openvpn package + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // Bind OpenVpnService for comm ops + if (!mBound){ + mPending = ACTION_START_EIP; + this.retreiveVpnService(); + } else { + if (mReceiver != null) { + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_START_EIP); + mReceiver.send(Activity.RESULT_OK, resultData); + } + } } private void stopEIP() { - OpenVpnManagementThread.stopOpenVPN(); + if (mBound){ + mVpnService.onRevoke(); + + /*if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_STOP_EIP); + mReceiver.send(Activity.RESULT_OK, resultData); + }*/ + } else { + // TODO If OpenVpnService isn't bound, does that really always mean it's not running? + // If it's not running, bindService doesn't work w/o START_SERVICE action, so... + /*mPending = ACTION_STOP_EIP; + this.retreiveVpnService();*/ + } + // Remove this if above comes back + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(ConfigHelper.REQUEST_TAG, ACTION_STOP_EIP); + mReceiver.send(Activity.RESULT_OK, resultData); + } } private void updateEIPService() { diff --git a/src/se/leap/openvpn/LaunchVPN.java b/src/se/leap/openvpn/LaunchVPN.java index 2dcaf176..1df6be96 100644 --- a/src/se/leap/openvpn/LaunchVPN.java +++ b/src/se/leap/openvpn/LaunchVPN.java @@ -19,6 +19,9 @@ package se.leap.openvpn; import java.io.IOException; import java.util.Collection; import java.util.Vector; + +import se.leap.leapclient.ConfigHelper; +import se.leap.leapclient.EIP; import se.leap.leapclient.R; import android.app.Activity; @@ -32,6 +35,7 @@ import android.content.SharedPreferences; import android.net.VpnService; import android.os.Bundle; import android.os.Parcelable; +import android.os.ResultReceiver; import android.preference.PreferenceManager; import android.text.InputType; import android.text.method.PasswordTransformationMethod; @@ -76,6 +80,8 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener { public static final int START_VPN_PROFILE= 70; + // Dashboard, maybe more, want to know! + private ResultReceiver mReceiver; private ProfileManager mPM; private VpnProfile mSelectedProfile; @@ -99,6 +105,9 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener { final Intent intent = getIntent(); final String action = intent.getAction(); + // If something wants feedback, they sent us a Receiver + mReceiver = intent.getParcelableExtra(ConfigHelper.RECEIVER_TAG); + // If the intent is a request to create a shortcut, we'll do that and exit @@ -273,7 +282,11 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener { new startOpenVpnThread().start(); } } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish + // User does not want us to start, so we just vanish (well, now we tell our receiver, then vanish) + Bundle resultData = new Bundle(); + // For now, nothing else is calling, so this "request" string is good enough + resultData.putString(ConfigHelper.REQUEST_TAG, EIP.ACTION_START_EIP); + mReceiver.send(RESULT_CANCELED, resultData); finish(); } } @@ -357,6 +370,11 @@ public class LaunchVPN extends ListActivity implements OnItemClickListener { @Override public void run() { VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + // Tell whom-it-may-concern that we started VPN + Bundle resultData = new Bundle(); + // For now, nothing else is calling, so this "request" string is good enough + resultData.putString(ConfigHelper.REQUEST_TAG, EIP.ACTION_START_EIP); + mReceiver.send(RESULT_OK, resultData); finish(); } diff --git a/src/se/leap/openvpn/OpenVpnService.java b/src/se/leap/openvpn/OpenVpnService.java index 42c1de8a..2408483d 100644 --- a/src/se/leap/openvpn/OpenVpnService.java +++ b/src/se/leap/openvpn/OpenVpnService.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Vector; + +import se.leap.leapclient.Dashboard; import se.leap.leapclient.R; import android.annotation.TargetApi; @@ -44,6 +46,7 @@ import se.leap.openvpn.OpenVPN.StateListener; public class OpenVpnService extends VpnService implements StateListener, Callback { public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE"; + public static final String RETRIEVE_SERVICE = "se.leap.openvpn.RETRIEVE_SERVICE"; private Thread mProcessThread=null; @@ -88,7 +91,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac @Override public IBinder onBind(Intent intent) { String action = intent.getAction(); - if( action !=null && action.equals(START_SERVICE)) + if( action !=null && (action.equals(START_SERVICE) || action.equals(RETRIEVE_SERVICE)) ) return mBinder; else return super.onBind(intent); @@ -222,7 +225,8 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac @Override public int onStartCommand(Intent intent, int flags, int startId) { - if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE)) + if( intent != null && intent.getAction() !=null && + (intent.getAction().equals(START_SERVICE) || intent.getAction().equals(RETRIEVE_SERVICE)) ) return START_NOT_STICKY; @@ -465,6 +469,13 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac mLocalIPv6 = ipv6addr; } + public boolean isRunning() { + if (mStarting == true || mProcessThread != null) + return true; + else + return false; + } + @Override public void updateState(String state,String logmessage, int resid) { // If the process is not running, ignore any state, -- cgit v1.2.3