diff options
14 files changed, 730 insertions, 723 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java index 262c6e9b..cace20ff 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java @@ -25,8 +25,10 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import junit.framework.Assert; @@ -54,7 +56,7 @@ import de.blinkt.openvpn.views.FileSelectLayout; import static de.blinkt.openvpn.views.FileSelectLayout.FileSelectCallback; -public class ConfigConverter extends Activity implements FileSelectCallback { +public class ConfigConverter extends Activity implements FileSelectCallback, View.OnClickListener { public static final String IMPORT_PROFILE = "de.blinkt.openvpn.IMPORT_PROFILE"; private static final int RESULT_INSTALLPKCS12 = 7; @@ -74,28 +76,39 @@ public class ConfigConverter extends Activity implements FileSelectCallback { private String mCrlFileName; @Override + public void onClick(View v) { + if (v.getId() == R.id.fab_save) + userActionSaveProfile(); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.cancel) { setResult(Activity.RESULT_CANCELED); finish(); } else if (item.getItemId() == R.id.ok) { - if (mResult == null) { - log("Importing the config had error, cannot save it"); - return true; - } + return userActionSaveProfile(); + } - Intent in = installPKCS12(); + return super.onOptionsItemSelected(item); - if (in != null) - startActivityForResult(in, RESULT_INSTALLPKCS12); - else - saveProfile(); + } + private boolean userActionSaveProfile() { + if (mResult == null) { + log(R.string.import_config_error); + Toast.makeText(this, R.string.import_config_error, Toast.LENGTH_LONG).show(); return true; } - return super.onOptionsItemSelected(item); + Intent in = installPKCS12(); + + if (in != null) + startActivityForResult(in, RESULT_INSTALLPKCS12); + else + saveProfile(); + return true; } @Override @@ -511,6 +524,10 @@ public class ConfigConverter extends Activity implements FileSelectCallback { setContentView(R.layout.config_converter); super.onCreate(savedInstanceState); + ImageButton fab_button = (ImageButton) findViewById(R.id.fab_save); + if(fab_button!=null) + fab_button.setOnClickListener(this); + if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) { mResult = (VpnProfile) savedInstanceState.getSerializable(VPNPROFILE); mAliasName = savedInstanceState.getString("mAliasName"); @@ -662,5 +679,4 @@ public class ConfigConverter extends Activity implements FileSelectCallback { log(getString(ressourceId, formatArgs)); } - } diff --git a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java index 1554a557..4aa7e120 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java @@ -9,6 +9,7 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Build; import android.support.v4n.view.ViewPager; import android.view.Menu; @@ -18,6 +19,7 @@ import de.blinkt.openvpn.R; import de.blinkt.openvpn.fragments.AboutFragment; import de.blinkt.openvpn.fragments.FaqFragment; import de.blinkt.openvpn.fragments.GeneralSettings; +import de.blinkt.openvpn.fragments.LogFragment; import de.blinkt.openvpn.fragments.SendDumpFragment; import de.blinkt.openvpn.fragments.VPNProfileList; import de.blinkt.openvpn.views.ScreenSlidePagerAdapter; @@ -57,7 +59,8 @@ public class MainActivity extends Activity { mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment.class); } - //mPagerAdapter.addTab(R.string.openvpn_log, LogFragment.class); + if (isDirectToTV()) + mPagerAdapter.addTab(R.string.openvpn_log, LogFragment.class); mPagerAdapter.addTab(R.string.about, AboutFragment.class); mPager.setAdapter(mPagerAdapter); @@ -66,6 +69,11 @@ public class MainActivity extends Activity { tabs.setViewPager(mPager); } + private boolean isDirectToTV() { + return(getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) + || getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)); + } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void disableToolbarElevation() { ActionBar toolbar = getActionBar(); diff --git a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java index 89a98241..2420194d 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java @@ -38,5 +38,4 @@ public class ICSOpenVPNApplication extends Application { //ACRA.init(this); } } - } diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 113142b7..9716f020 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -16,7 +16,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.ConnectivityManager; -import android.net.NetworkRequest; import android.net.VpnService; import android.os.Binder; import android.os.Build; @@ -62,7 +61,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN"; private static final int OPENVPN_STATUS = 1; private static boolean mNotificationAlwaysVisible = false; - private final Vector<String> mDnslist = new Vector<String>(); + private final Vector<String> mDnslist = new Vector<>(); private final NetworkSpace mRoutes = new NetworkSpace(); private final NetworkSpace mRoutesv6 = new NetworkSpace(); private final IBinder mBinder = new LocalBinder(); @@ -81,7 +80,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private String mLastTunCfg; private String mRemoteGW; private final Object mProcessLock = new Object(); - private LollipopDeviceStateListener mLollipopDeviceStateListener; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java public static String humanReadableByteCount(long bytes, boolean mbit) { @@ -235,13 +233,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac //ignore exception - } catch (NoSuchMethodException nsm) { - VpnStatus.logException(nsm); - } catch (IllegalArgumentException e) { - VpnStatus.logException(e); - } catch (IllegalAccessException e) { - VpnStatus.logException(e); - } catch (InvocationTargetException e) { + } catch (NoSuchMethodException | IllegalArgumentException | + InvocationTargetException | IllegalAccessException e) { VpnStatus.logException(e); } @@ -325,6 +318,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac /* The intent is null when the service has been restarted */ if (intent == null) { mProfile = ProfileManager.getLastConnectedProfile(this, false); + VpnStatus.logInfo(R.string.service_restarted); /* Got no profile, just stop */ if (mProfile == null) { @@ -412,7 +406,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { - HashMap<String, String> env = new HashMap<String, String>(); + HashMap<String, String> env = new HashMap<>(); processThread = new OpenVPNThread(this, argv, env, nativeLibraryDirectory); } @@ -428,7 +422,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac ProfileManager.setConnectedVpnProfile(this, mProfile); /* TODO: At the moment we have no way to handle asynchronous PW input - * Fixing will also allow to handle challenge/responsee authentication */ + * Fixing will also allow to handle challenge/response authentication */ if (mProfile.needUserPWInput(true) != 0) return START_NOT_STICKY; @@ -439,17 +433,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac try { Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3"); return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class, VpnProfile.class).newInstance(this, mProfile); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { + } catch (IllegalArgumentException | InstantiationException | InvocationTargetException | + NoSuchMethodException | ClassNotFoundException | IllegalAccessException e ) { e.printStackTrace(); } return null; @@ -572,7 +557,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } } - if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist != null && mDnslist.size() >= 1) { + if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) { // Check if the first DNS Server is in the VPN range try { ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true); @@ -652,22 +637,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac builder.allowFamily(OsConstants.AF_INET6); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - void removeLollipopCMListener() { - ConnectivityManager cm = (ConnectivityManager) getBaseContext().getSystemService(CONNECTIVITY_SERVICE); - cm.unregisterNetworkCallback(mLollipopDeviceStateListener); - mLollipopDeviceStateListener = null; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - void addLollipopCMListener() { - ConnectivityManager cm = (ConnectivityManager) getBaseContext().getSystemService(CONNECTIVITY_SERVICE); - NetworkRequest.Builder nrb = new NetworkRequest.Builder(); - - mLollipopDeviceStateListener = new LollipopDeviceStateListener(); - cm.registerNetworkCallback(nrb.build(), mLollipopDeviceStateListener); - } - private void addLocalNetworksToRoutes() { // Add local network interfaces @@ -879,8 +848,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // CONNECTED // Does not work :( String msg = getString(resid); - String ticker = msg; - showNotification(msg + " " + logmessage, ticker, lowpriority, 0, level); + showNotification(msg + " " + logmessage, msg, lowpriority, 0, level); } } diff --git a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java index e4687c3c..086cdb44 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java @@ -5,11 +5,15 @@ package de.blinkt.openvpn.core; -import java.io.FileNotFoundException; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.StreamCorruptedException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -17,207 +21,173 @@ import java.util.Set; import de.blinkt.openvpn.VpnProfile; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; - public class ProfileManager { - private static final String PREFS_NAME = "VPNList"; + private static final String PREFS_NAME = "VPNList"; + private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile"; + private static ProfileManager instance; + private static VpnProfile mLastConnectedVpn = null; + private HashMap<String, VpnProfile> profiles = new HashMap<>(); + private static VpnProfile tmpprofile = null; - private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile"; + private static VpnProfile get(String key) { + if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) + return tmpprofile; + if (instance == null) + return null; + return instance.profiles.get(key); + + } - private static ProfileManager instance; + private ProfileManager() { + } + private static void checkInstance(Context context) { + if (instance == null) { + instance = new ProfileManager(); + instance.loadVPNList(context); + } + } - private static VpnProfile mLastConnectedVpn=null; - private HashMap<String,VpnProfile> profiles=new HashMap<String, VpnProfile>(); - private static VpnProfile tmpprofile=null; + synchronized public static ProfileManager getInstance(Context context) { + checkInstance(context); + return instance; + } + public static void setConntectedVpnProfileDisconnected(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + prefsedit.putString(LAST_CONNECTED_PROFILE, null); + prefsedit.apply(); - private static VpnProfile get(String key) { - if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) - return tmpprofile; - - if(instance==null) - return null; - return instance.profiles.get(key); - - } + } + public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); - - private ProfileManager() { } - - private static void checkInstance(Context context) { - if(instance == null) { - instance = new ProfileManager(); - instance.loadVPNList(context); - } - } + prefsedit.putString(LAST_CONNECTED_PROFILE, connectedrofile.getUUIDString()); + prefsedit.apply(); + mLastConnectedVpn = connectedrofile; - synchronized public static ProfileManager getInstance(Context context) { - checkInstance(context); - return instance; - } - - public static void setConntectedVpnProfileDisconnected(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - prefsedit.putString(LAST_CONNECTED_PROFILE, null); - prefsedit.apply(); - - } + } - public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - - prefsedit.putString(LAST_CONNECTED_PROFILE, connectedrofile.getUUIDString()); - prefsedit.apply(); - mLastConnectedVpn=connectedrofile; - - } - - public static VpnProfile getLastConnectedProfile(Context c, boolean onBoot) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + public static VpnProfile getLastConnectedProfile(Context c, boolean onBoot) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); + boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); if (onBoot && !useStartOnBoot) return null; - - String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null); - if(lastConnectedProfile!=null) - return get(c, lastConnectedProfile); - else - return null; - } - - - - - public Collection<VpnProfile> getProfiles() { - return profiles.values(); - } - - public VpnProfile getProfileByName(String name) { - for (VpnProfile vpnp : profiles.values()) { - if(vpnp.getName().equals(name)) { - return vpnp; - } - } - return null; - } - - public void saveProfileList(Context context) { - SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Editor editor = sharedprefs.edit(); - editor.putStringSet("vpnlist", profiles.keySet()); - - // For reasing I do not understand at all - // Android saves my prefs file only one time - // if I remove the debug code below :( - int counter = sharedprefs.getInt("counter", 0); - editor.putInt("counter", counter+1); - editor.apply(); - - } - - public void addProfile(VpnProfile profile) { - profiles.put(profile.getUUID().toString(),profile); - - } - - public static void setTemporaryProfile(VpnProfile tmp) { - ProfileManager.tmpprofile = tmp; - } - - - public void saveProfile(Context context, VpnProfile profile) { - ObjectOutputStream vpnfile; - try { - vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); - - vpnfile.writeObject(profile); - vpnfile.flush(); - vpnfile.close(); - } catch (IOException e) { - VpnStatus.logException("saving VPN profile", e); - throw new RuntimeException(e); - } - } - - - private void loadVPNList(Context context) { - profiles = new HashMap<String, VpnProfile>(); - SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Set<String> vlist = listpref.getStringSet("vpnlist", null); - Exception exp =null; - if(vlist==null){ - vlist = new HashSet<String>(); - } - - for (String vpnentry : vlist) { - try { - ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); - VpnProfile vp = ((VpnProfile) vpnfile.readObject()); - - // Sanity check - if(vp==null || vp.mName==null || vp.getUUID()==null) - continue; - vp.upgradeProfile(); - profiles.put(vp.getUUID().toString(), vp); + String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null); + if (lastConnectedProfile != null) + return get(c, lastConnectedProfile); + else + return null; + } + + + public Collection<VpnProfile> getProfiles() { + return profiles.values(); + } + + public VpnProfile getProfileByName(String name) { + for (VpnProfile vpnp : profiles.values()) { + if (vpnp.getName().equals(name)) { + return vpnp; + } + } + return null; + } + + public void saveProfileList(Context context) { + SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); + Editor editor = sharedprefs.edit(); + editor.putStringSet("vpnlist", profiles.keySet()); + + // For reasing I do not understand at all + // Android saves my prefs file only one time + // if I remove the debug code below :( + int counter = sharedprefs.getInt("counter", 0); + editor.putInt("counter", counter + 1); + editor.apply(); + + } + + public void addProfile(VpnProfile profile) { + profiles.put(profile.getUUID().toString(), profile); - } catch (StreamCorruptedException e) { - exp=e; - } catch (FileNotFoundException e) { - exp=e; - } catch (IOException e) { - exp=e; - } catch (ClassNotFoundException e) { - exp=e; - } - if(exp!=null) { - VpnStatus.logException("Loading VPN List",exp); - } - } - } + } - public int getNumberOfProfiles() { - return profiles.size(); - } + public static void setTemporaryProfile(VpnProfile tmp) { + ProfileManager.tmpprofile = tmp; + } + public void saveProfile(Context context, VpnProfile profile) { + ObjectOutputStream vpnfile; + try { + vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE)); - public void removeProfile(Context context,VpnProfile profile) { - String vpnentry = profile.getUUID().toString(); - profiles.remove(vpnentry); - saveProfileList(context); - context.deleteFile(vpnentry + ".vp"); - if(mLastConnectedVpn==profile) - mLastConnectedVpn=null; - - } + vpnfile.writeObject(profile); + vpnfile.flush(); + vpnfile.close(); + } catch (IOException e) { + VpnStatus.logException("saving VPN profile", e); + throw new RuntimeException(e); + } + } + + + private void loadVPNList(Context context) { + profiles = new HashMap<>(); + SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); + Set<String> vlist = listpref.getStringSet("vpnlist", null); + if (vlist == null) { + vlist = new HashSet<>(); + } + + for (String vpnentry : vlist) { + try { + ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); + VpnProfile vp = ((VpnProfile) vpnfile.readObject()); + + // Sanity check + if (vp == null || vp.mName == null || vp.getUUID() == null) + continue; + + vp.upgradeProfile(); + profiles.put(vp.getUUID().toString(), vp); + } catch (IOException | ClassNotFoundException e) { + VpnStatus.logException("Loading VPN List", e); + } + } + } - public static VpnProfile get(Context context, String profileUUID) { - checkInstance(context); - return get(profileUUID); - } + public void removeProfile(Context context, VpnProfile profile) { + String vpnentry = profile.getUUID().toString(); + profiles.remove(vpnentry); + saveProfileList(context); + context.deleteFile(vpnentry + ".vp"); + if (mLastConnectedVpn == profile) + mLastConnectedVpn = null; + } + public static VpnProfile get(Context context, String profileUUID) { + checkInstance(context); + return get(profileUUID); + } - public static VpnProfile getLastConnectedVpn() { - return mLastConnectedVpn; - } + public static VpnProfile getLastConnectedVpn() { + return mLastConnectedVpn; + } } diff --git a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index ea114d7e..d485a687 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -8,7 +8,6 @@ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.os.Build; import java.io.File; @@ -67,14 +66,14 @@ public class VPNLaunchHelper { public static String[] buildOpenvpnArgv(Context c) { - Vector<String> args = new Vector<String>(); + Vector<String> args = new Vector<>(); // Add fixed paramenters //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); args.add(writeMiniVPN(c)); args.add("--config"); - args.add(c.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); + args.add(getConfigFilePath(c)); return args.toArray(new String[args.size()]); } diff --git a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index 8ab65424..9d19a0e8 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -6,6 +6,7 @@ package de.blinkt.openvpn.core; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -16,12 +17,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import org.w3c.dom.Text; - -import de.blinkt.openvpn.R; - import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; +import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.security.MessageDigest; @@ -36,28 +33,30 @@ import java.util.Locale; import java.util.UnknownFormatConversionException; import java.util.Vector; +import de.blinkt.openvpn.R; + public class VpnStatus { - public static LinkedList<LogItem> logbuffer; + public static LinkedList<LogItem> logbuffer; - private static Vector<LogListener> logListener; - private static Vector<StateListener> stateListener; - private static Vector<ByteCountListener> byteCountListener; + private static Vector<LogListener> logListener; + private static Vector<StateListener> stateListener; + private static Vector<ByteCountListener> byteCountListener; - private static String mLaststatemsg=""; + private static String mLaststatemsg = ""; - private static String mLaststate = "NOPROCESS"; + private static String mLaststate = "NOPROCESS"; - private static int mLastStateresid=R.string.state_noprocess; + private static int mLastStateresid = R.string.state_noprocess; - private static long mlastByteCount[]={0,0,0,0}; + private static long mlastByteCount[] = {0, 0, 0, 0}; - public static void logException(LogLevel ll, String context, Exception e) { + public static void logException(LogLevel ll, String context, Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); LogItem li; - if (context !=null) { + if (context != null) { li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context); } else { li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString()); @@ -75,18 +74,16 @@ public class VpnStatus { private static final int MAXLOGENTRIES = 1000; - public static final String MANAGMENT_PREFIX = "M:"; - public enum ConnectionStatus { LEVEL_CONNECTED, LEVEL_VPNPAUSED, LEVEL_CONNECTING_SERVER_REPLIED, LEVEL_CONNECTING_NO_SERVER_REPLY_YET, LEVEL_NONETWORK, - LEVEL_NOTCONNECTED, - LEVEL_AUTH_FAILED, - LEVEL_WAITING_FOR_USER_INPUT, - UNKNOWN_LEVEL + LEVEL_NOTCONNECTED, + LEVEL_AUTH_FAILED, + LEVEL_WAITING_FOR_USER_INPUT, + UNKNOWN_LEVEL } public enum LogLevel { @@ -97,6 +94,7 @@ public class VpnStatus { DEBUG(4); protected int mValue; + LogLevel(int value) { mValue = value; } @@ -107,59 +105,64 @@ public class VpnStatus { public static LogLevel getEnumByValue(int value) { switch (value) { - case 1: return INFO; - case 2: return ERROR; - case 3: return WARNING; - case 4: return DEBUG; - default: return null; + case 1: + return INFO; + case 2: + return ERROR; + case 3: + return WARNING; + case 4: + return DEBUG; + default: + return null; } } } // keytool -printcert -jarfile de.blinkt.openvpn_85.apk - public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; - public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; - public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; + public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; + public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; + public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; - private static ConnectionStatus mLastLevel=ConnectionStatus.LEVEL_NOTCONNECTED; + private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; - static { - logbuffer = new LinkedList<LogItem>(); - logListener = new Vector<VpnStatus.LogListener>(); - stateListener = new Vector<VpnStatus.StateListener>(); - byteCountListener = new Vector<VpnStatus.ByteCountListener>(); - logInformation(); - } + static { + logbuffer = new LinkedList<>(); + logListener = new Vector<>(); + stateListener = new Vector<>(); + byteCountListener = new Vector<>(); + logInformation(); + } - public static class LogItem implements Parcelable { + public static class LogItem implements Parcelable { - private Object [] mArgs = null; - private String mMessage = null; - private int mRessourceId; - // Default log priority - LogLevel mLevel = LogLevel.INFO; - private long logtime = System.currentTimeMillis(); + private Object[] mArgs = null; + private String mMessage = null; + private int mRessourceId; + // Default log priority + LogLevel mLevel = LogLevel.INFO; + private long logtime = System.currentTimeMillis(); private int mVerbosityLevel = -1; - private LogItem(int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - } + private LogItem(int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + } public LogItem(LogLevel level, int verblevel, String message) { - mMessage=message; + mMessage = message; mLevel = level; mVerbosityLevel = verblevel; } @Override - public int describeContents() { - return 0; - } + public int describeContents() { + return 0; + } @Override @@ -173,65 +176,65 @@ public class VpnStatus { dest.writeLong(logtime); } - public LogItem(Parcel in) { - mArgs = in.readArray(Object.class.getClassLoader()); - mMessage = in.readString(); - mRessourceId = in.readInt(); - mLevel = LogLevel.getEnumByValue(in.readInt()); + public LogItem(Parcel in) { + mArgs = in.readArray(Object.class.getClassLoader()); + mMessage = in.readString(); + mRessourceId = in.readInt(); + mLevel = LogLevel.getEnumByValue(in.readInt()); mVerbosityLevel = in.readInt(); - logtime = in.readLong(); - } + logtime = in.readLong(); + } - public static final Parcelable.Creator<LogItem> CREATOR - = new Parcelable.Creator<LogItem>() { - public LogItem createFromParcel(Parcel in) { - return new LogItem(in); - } + public static final Parcelable.Creator<LogItem> CREATOR + = new Parcelable.Creator<LogItem>() { + public LogItem createFromParcel(Parcel in) { + return new LogItem(in); + } - public LogItem[] newArray(int size) { - return new LogItem[size]; - } - }; + public LogItem[] newArray(int size) { + return new LogItem[size]; + } + }; - public LogItem(LogLevel loglevel,int ressourceId, Object... args) { + public LogItem(LogLevel loglevel, int ressourceId, Object... args) { mRessourceId = ressourceId; - mArgs =args; + mArgs = args; mLevel = loglevel; } - public LogItem(LogLevel loglevel, String msg) { - mLevel = loglevel; - mMessage = msg; - } + public LogItem(LogLevel loglevel, String msg) { + mLevel = loglevel; + mMessage = msg; + } - public LogItem(LogLevel loglevel, int ressourceId) { - mRessourceId =ressourceId; - mLevel = loglevel; - } + public LogItem(LogLevel loglevel, int ressourceId) { + mRessourceId = ressourceId; + mLevel = loglevel; + } - public String getString(Context c) { + public String getString(Context c) { try { - if(mMessage !=null) { - return mMessage; - } else { - if(c!=null) { - if(mRessourceId==R.string.mobile_info) - return getMobileInfoString(c); - if(mArgs == null) - return c.getString(mRessourceId); - else - return c.getString(mRessourceId, mArgs); - } else { - String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); - if(mArgs !=null) - str += TextUtils.join("|", mArgs); - - - return str; - } - } + if (mMessage != null) { + return mMessage; + } else { + if (c != null) { + if (mRessourceId == R.string.mobile_info) + return getMobileInfoString(c); + if (mArgs == null) + return c.getString(mRessourceId); + else + return c.getString(mRessourceId, mArgs); + } else { + String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId); + if (mArgs != null) + str += TextUtils.join("|", mArgs); + + + return str; + } + } } catch (UnknownFormatConversionException e) { if (c != null) throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null)); @@ -239,68 +242,66 @@ public class VpnStatus { throw e; } catch (java.util.FormatFlagsConversionMismatchException e) { if (c != null) - throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null),e.getConversion()); + throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion()); else throw e; } - } + } - public LogLevel getLogLevel() - { + public LogLevel getLogLevel() { return mLevel; } // The lint is wrong here - @SuppressLint("StringFormatMatches") - private String getMobileInfoString(Context c) { - c.getPackageManager(); - String apksign="error getting package signature"; - - String version="error getting version"; - try { - Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] der = cert.getEncoded(); - md.update(der); - byte[] digest = md.digest(); - - if (Arrays.equals(digest, officalkey)) - apksign = c.getString(R.string.official_build); - else if (Arrays.equals(digest, officaldebugkey)) - apksign = c.getString(R.string.debug_build); - else if (Arrays.equals(digest, amazonkey)) - apksign = "amazon version"; - else if (Arrays.equals(digest, fdroidkey)) + @SuppressLint("StringFormatMatches") + private String getMobileInfoString(Context c) { + c.getPackageManager(); + String apksign = "error getting package signature"; + + String version = "error getting version"; + try { + Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] der = cert.getEncoded(); + md.update(der); + byte[] digest = md.digest(); + + if (Arrays.equals(digest, officalkey)) + apksign = c.getString(R.string.official_build); + else if (Arrays.equals(digest, officaldebugkey)) + apksign = c.getString(R.string.debug_build); + else if (Arrays.equals(digest, amazonkey)) + apksign = "amazon version"; + else if (Arrays.equals(digest, fdroidkey)) apksign = "F-Droid built and signed version"; else - apksign = c.getString(R.string.built_by,cert.getSubjectX500Principal().getName()); + apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName()); - PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); - version = packageinfo.versionName; + PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); + version = packageinfo.versionName; - } catch (NameNotFoundException e) { - } catch (CertificateException e) { - } catch (NoSuchAlgorithmException e) { - } + } catch (NameNotFoundException | CertificateException | + NoSuchAlgorithmException ignored) { + } - Object[] argsext = Arrays.copyOf(mArgs, mArgs.length+2); - argsext[argsext.length-1]=apksign; - argsext[argsext.length-2]=version; + Object[] argsext = Arrays.copyOf(mArgs, mArgs.length + 2); + argsext[argsext.length - 1] = apksign; + argsext[argsext.length - 2] = version; - return c.getString(R.string.mobile_info_extended, argsext); + return c.getString(R.string.mobile_info_extended, argsext); - } + } - public long getLogtime() { - return logtime; - } + public long getLogtime() { + return logtime; + } public int getVerbosityLevel() { - if (mVerbosityLevel==-1) { + if (mVerbosityLevel == -1) { // Hack: // For message not from OpenVPN, report the status level as log level return mLevel.getInt(); @@ -309,90 +310,94 @@ public class VpnStatus { } } + public void saveLogToDisk(Context c) { + + new File(c.getCacheDir(), "log.xml"); - public interface LogListener { - void newLog(LogItem logItem); - } - - public interface StateListener { - void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); - } - - public interface ByteCountListener { - void updateByteCount(long in, long out, long diffIn, long diffOut); - } - - public synchronized static void logMessage(LogLevel level,String prefix, String message) - { - newLogItem(new LogItem(level, prefix + message)); - - } - - public synchronized static void clearLog() { - logbuffer.clear(); - logInformation(); - } - - private static void logInformation() { - logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); - } - - public synchronized static void addLogListener(LogListener ll){ - logListener.add(ll); - } - - public synchronized static void removeLogListener(LogListener ll) { - logListener.remove(ll); - } - - public synchronized static void addByteCountListener(ByteCountListener bcl) { - bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); - byteCountListener.add(bcl); - } - - public synchronized static void removeByteCountListener(ByteCountListener bcl) { - byteCountListener.remove(bcl); - } - - - public synchronized static void addStateListener(StateListener sl){ - if(!stateListener.contains(sl)){ - stateListener.add(sl); - if(mLaststate!=null) - sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); - } - } - - private static int getLocalizedState(String state){ - if (state.equals("CONNECTING")) - return R.string.state_connecting; - else if (state.equals("WAIT")) - return R.string.state_wait; - else if (state.equals("AUTH")) - return R.string.state_auth; - else if (state.equals("GET_CONFIG")) - return R.string.state_get_config; - else if (state.equals("ASSIGN_IP")) - return R.string.state_assign_ip; - else if (state.equals("ADD_ROUTES")) - return R.string.state_add_routes; - else if (state.equals("CONNECTED")) - return R.string.state_connected; - else if (state.equals("DISCONNECTED")) - return R.string.state_disconnected; - else if (state.equals("RECONNECTING")) - return R.string.state_reconnecting; - else if (state.equals("EXITING")) - return R.string.state_exiting; - else if (state.equals("RESOLVE")) - return R.string.state_resolve; - else if (state.equals("TCP_CONNECT")) - return R.string.state_tcp_connect; - else - return R.string.unknown_state; - - } + } + + public interface LogListener { + void newLog(LogItem logItem); + } + + public interface StateListener { + void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); + } + + public interface ByteCountListener { + void updateByteCount(long in, long out, long diffIn, long diffOut); + } + + public synchronized static void logMessage(LogLevel level, String prefix, String message) { + newLogItem(new LogItem(level, prefix + message)); + + } + + public synchronized static void clearLog() { + logbuffer.clear(); + logInformation(); + } + + private static void logInformation() { + logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT); + } + + public synchronized static void addLogListener(LogListener ll) { + logListener.add(ll); + } + + public synchronized static void removeLogListener(LogListener ll) { + logListener.remove(ll); + } + + public synchronized static void addByteCountListener(ByteCountListener bcl) { + bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); + byteCountListener.add(bcl); + } + + public synchronized static void removeByteCountListener(ByteCountListener bcl) { + byteCountListener.remove(bcl); + } + + + public synchronized static void addStateListener(StateListener sl) { + if (!stateListener.contains(sl)) { + stateListener.add(sl); + if (mLaststate != null) + sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); + } + } + + private static int getLocalizedState(String state) { + if (state.equals("CONNECTING")) + return R.string.state_connecting; + else if (state.equals("WAIT")) + return R.string.state_wait; + else if (state.equals("AUTH")) + return R.string.state_auth; + else if (state.equals("GET_CONFIG")) + return R.string.state_get_config; + else if (state.equals("ASSIGN_IP")) + return R.string.state_assign_ip; + else if (state.equals("ADD_ROUTES")) + return R.string.state_add_routes; + else if (state.equals("CONNECTED")) + return R.string.state_connected; + else if (state.equals("DISCONNECTED")) + return R.string.state_disconnected; + else if (state.equals("RECONNECTING")) + return R.string.state_reconnecting; + else if (state.equals("EXITING")) + return R.string.state_exiting; + else if (state.equals("RESOLVE")) + return R.string.state_resolve; + else if (state.equals("TCP_CONNECT")) + return R.string.state_tcp_connect; + else + return R.string.unknown_state; + + } public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) { switch (pauseReason) { @@ -409,88 +414,84 @@ public class VpnStatus { } - private static ConnectionStatus getLevel(String state){ - String[] noreplyet = {"CONNECTING","WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; - String[] reply = {"AUTH","GET_CONFIG","ASSIGN_IP","ADD_ROUTES"}; - String[] connected = {"CONNECTED"}; - String[] notconnected = {"DISCONNECTED", "EXITING"}; - - for(String x:noreplyet) - if(state.equals(x)) - return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; - - for(String x:reply) - if(state.equals(x)) - return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; + private static ConnectionStatus getLevel(String state) { + String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; + String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"}; + String[] connected = {"CONNECTED"}; + String[] notconnected = {"DISCONNECTED", "EXITING"}; - for(String x:connected) - if(state.equals(x)) - return ConnectionStatus.LEVEL_CONNECTED; + for (String x : noreplyet) + if (state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; - for(String x:notconnected) - if(state.equals(x)) - return ConnectionStatus.LEVEL_NOTCONNECTED; + for (String x : reply) + if (state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; - return ConnectionStatus.UNKNOWN_LEVEL; + for (String x : connected) + if (state.equals(x)) + return ConnectionStatus.LEVEL_CONNECTED; - } + for (String x : notconnected) + if (state.equals(x)) + return ConnectionStatus.LEVEL_NOTCONNECTED; + return ConnectionStatus.UNKNOWN_LEVEL; + } - public synchronized static void removeStateListener(StateListener sl) { - stateListener.remove(sl); - } + public synchronized static void removeStateListener(StateListener sl) { + stateListener.remove(sl); + } - synchronized public static LogItem[] getlogbuffer() { + synchronized public static LogItem[] getlogbuffer() { - // The stoned way of java to return an array from a vector - // brought to you by eclipse auto complete - return logbuffer.toArray(new LogItem[logbuffer.size()]); + // The stoned way of java to return an array from a vector + // brought to you by eclipse auto complete + return logbuffer.toArray(new LogItem[logbuffer.size()]); - } + } - public static void updateStateString (String state, String msg) { - int rid = getLocalizedState(state); - ConnectionStatus level = getLevel(state); - updateStateString(state, msg, rid, level); - } + public static void updateStateString(String state, String msg) { + int rid = getLocalizedState(state); + ConnectionStatus level = getLevel(state); + updateStateString(state, msg, rid, level); + } - public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) { + public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) { // Workound for OpenVPN doing AUTH and wait and being connected // Simply ignore these state if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && - (state.equals("WAIT") || state.equals("AUTH"))) - { - newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s",state,level.toString(),msg))); + (state.equals("WAIT") || state.equals("AUTH"))) { + newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg))); return; } - mLaststate= state; - mLaststatemsg = msg; - mLastStateresid = resid; - mLastLevel = level; - + mLaststate = state; + mLaststatemsg = msg; + mLastStateresid = resid; + mLastLevel = level; for (StateListener sl : stateListener) { - sl.updateState(state,msg,resid,level); - } + sl.updateState(state, msg, resid, level); + } //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); - } + } - public static void logInfo(String message) { - newLogItem(new LogItem(LogLevel.INFO, message)); - } + public static void logInfo(String message) { + newLogItem(new LogItem(LogLevel.INFO, message)); + } public static void logDebug(String message) { newLogItem(new LogItem(LogLevel.DEBUG, message)); } public static void logInfo(int resourceId, Object... args) { - newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); - } + newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); + } public static void logDebug(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); @@ -498,19 +499,19 @@ public class VpnStatus { private synchronized static void newLogItem(LogItem logItem) { - logbuffer.addLast(logItem); - if(logbuffer.size()>MAXLOGENTRIES) - logbuffer.removeFirst(); + logbuffer.addLast(logItem); + if (logbuffer.size() > MAXLOGENTRIES) + logbuffer.removeFirst(); - for (LogListener ll : logListener) { - ll.newLog(logItem); - } - } + for (LogListener ll : logListener) { + ll.newLog(logItem); + } + } - public static void logError(String msg) { - newLogItem(new LogItem(LogLevel.ERROR, msg)); + public static void logError(String msg) { + newLogItem(new LogItem(LogLevel.ERROR, msg)); - } + } public static void logWarning(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.WARNING, resourceId, args)); @@ -522,11 +523,12 @@ public class VpnStatus { public static void logError(int resourceId) { - newLogItem(new LogItem(LogLevel.ERROR, resourceId)); - } - public static void logError(int resourceId, Object... args) { - newLogItem(new LogItem(LogLevel.ERROR, resourceId, args)); - } + newLogItem(new LogItem(LogLevel.ERROR, resourceId)); + } + + public static void logError(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.ERROR, resourceId, args)); + } public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) { newLogItem(new LogItem(level, ovpnlevel, message)); @@ -535,19 +537,17 @@ public class VpnStatus { public static synchronized void updateByteCount(long in, long out) { - long lastIn = mlastByteCount[0]; - long lastOut = mlastByteCount[1]; - long diffIn = mlastByteCount[2] = in - lastIn; - long diffOut = mlastByteCount[3] = out - lastOut; - - - - mlastByteCount = new long[] {in,out,diffIn,diffOut}; - for(ByteCountListener bcl:byteCountListener){ - bcl.updateByteCount(in, out, diffIn,diffOut); - } - } + long lastIn = mlastByteCount[0]; + long lastOut = mlastByteCount[1]; + long diffIn = mlastByteCount[2] = in - lastIn; + long diffOut = mlastByteCount[3] = out - lastOut; + + mlastByteCount = new long[]{in, out, diffIn, diffOut}; + for (ByteCountListener bcl : byteCountListener) { + bcl.updateByteCount(in, out, diffIn, diffOut); + } + } } diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java index e15fe509..cf712e7c 100644 --- a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java +++ b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java @@ -22,8 +22,10 @@ import android.view.*; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; + import de.blinkt.openvpn.*; import de.blinkt.openvpn.activities.ConfigConverter; import de.blinkt.openvpn.activities.FileSelect; @@ -34,120 +36,120 @@ import java.util.Collection; import java.util.Comparator; import java.util.TreeSet; -public class VPNProfileList extends ListFragment { +public class VPNProfileList extends ListFragment implements OnClickListener { - public final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; - public final static int RESULT_VPN_DUPLICATE = Activity.RESULT_FIRST_USER +1 ; + public final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; + public final static int RESULT_VPN_DUPLICATE = Activity.RESULT_FIRST_USER + 1; - private static final int MENU_ADD_PROFILE = Menu.FIRST; + private static final int MENU_ADD_PROFILE = Menu.FIRST; - private static final int START_VPN_CONFIG = 92; - private static final int SELECT_PROFILE = 43; - private static final int IMPORT_PROFILE = 231; + private static final int START_VPN_CONFIG = 92; + private static final int SELECT_PROFILE = 43; + private static final int IMPORT_PROFILE = 231; private static final int FILE_PICKER_RESULT_KITKAT = 392; - private static final int MENU_IMPORT_PROFILE = Menu.FIRST +1; + private static final int MENU_IMPORT_PROFILE = Menu.FIRST + 1; class VPNArrayAdapter extends ArrayAdapter<VpnProfile> { - public VPNArrayAdapter(Context context, int resource, - int textViewResourceId) { - super(context, resource, textViewResourceId); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - View titleview = v.findViewById(R.id.vpn_list_item_left); - titleview.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - VpnProfile profile =(VpnProfile) getListAdapter().getItem(position); - startVPN(profile); - } - }); - - View settingsview = v.findViewById(R.id.quickedit_settings); - settingsview.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - VpnProfile editProfile = (VpnProfile) getListAdapter().getItem(position); - editVPN(editProfile); - - } - }); - - return v; - } - } - - - + public VPNArrayAdapter(Context context, int resource, + int textViewResourceId) { + super(context, resource, textViewResourceId); + } + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + View titleview = v.findViewById(R.id.vpn_list_item_left); + titleview.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + VpnProfile profile = (VpnProfile) getListAdapter().getItem(position); + startVPN(profile); + } + }); + + View settingsview = v.findViewById(R.id.quickedit_settings); + settingsview.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + VpnProfile editProfile = (VpnProfile) getListAdapter().getItem(position); + editVPN(editProfile); + + } + }); + + return v; + } + } + private ArrayAdapter<VpnProfile> mArrayadapter; + protected VpnProfile mEditProfile = null; - private ArrayAdapter<VpnProfile> mArrayadapter; - protected VpnProfile mEditProfile=null; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); + class MiniImageGetter implements ImageGetter { - } + @Override + public Drawable getDrawable(String source) { + Drawable d = null; + if ("ic_menu_add".equals(source)) + d = getActivity().getResources().getDrawable(R.drawable.ic_menu_add_grey); + else if ("ic_menu_archive".equals(source)) + d = getActivity().getResources().getDrawable(R.drawable.ic_menu_import_grey); + + + if (d != null) { + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + return d; + } else { + return null; + } + } + } - class MiniImageGetter implements ImageGetter { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.vpn_profile_list, container, false); - @Override - public Drawable getDrawable(String source) { - Drawable d=null; - if ("ic_menu_add".equals(source)) - d = getActivity().getResources().getDrawable(R.drawable.ic_menu_add_grey); - else if("ic_menu_archive".equals(source)) - d = getActivity().getResources().getDrawable(R.drawable.ic_menu_import_grey); - - - - if(d!=null) { - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - return d; - }else{ - return null; - } - } - } + TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); + TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); + newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint), new MiniImageGetter(), null)); + importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint), new MiniImageGetter(), null)); - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.vpn_profile_list, container,false); + ImageButton fab_add = (ImageButton) v.findViewById(R.id.fab_add); + ImageButton fab_import = (ImageButton) v.findViewById(R.id.fab_import); + if (fab_add != null) + fab_add.setOnClickListener(this); - TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); - TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); - - newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null)); - importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null)); + if (fab_import != null) + fab_import.setOnClickListener(this); - return v; + return v; - } + } - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setListAdapter(); - } + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(); + } static class VpnProfileNameComparator implements Comparator<VpnProfile> { @@ -172,52 +174,63 @@ public class VPNProfileList extends ListFragment { } - private void setListAdapter() { - mArrayadapter = new VPNArrayAdapter(getActivity(),R.layout.vpn_list_item,R.id.vpn_item_title); - Collection<VpnProfile> allvpn = getPM().getProfiles(); + private void setListAdapter() { + mArrayadapter = new VPNArrayAdapter(getActivity(), R.layout.vpn_list_item, R.id.vpn_item_title); + Collection<VpnProfile> allvpn = getPM().getProfiles(); TreeSet<VpnProfile> sortedset = new TreeSet<VpnProfile>(new VpnProfileNameComparator()); sortedset.addAll(allvpn); mArrayadapter.addAll(sortedset); setListAdapter(mArrayadapter); - } - + } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_ADD_PROFILE, 0 , R.string.menu_add_profile) - .setIcon(R.drawable.ic_menu_add) - .setAlphabeticShortcut('a') - .setTitleCondensed(getActivity().getString(R.string.add)) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_ADD_PROFILE, 0, R.string.menu_add_profile) + .setIcon(R.drawable.ic_menu_add) + .setAlphabeticShortcut('a') + .setTitleCondensed(getActivity().getString(R.string.add)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) - .setIcon(R.drawable.ic_menu_import) - .setAlphabeticShortcut('i') - .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT ); - } + menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) + .setIcon(R.drawable.ic_menu_import) + .setAlphabeticShortcut('i') + .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == MENU_ADD_PROFILE) { - onAddOrDuplicateProfile(null); - return true; - } else if (itemId == MENU_IMPORT_PROFILE) { + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == MENU_ADD_PROFILE) { + onAddOrDuplicateProfile(null); + return true; + } else if (itemId == MENU_IMPORT_PROFILE) { return startImportConfigFilePicker(); - } else { - return super.onOptionsItemSelected(item); - } - } + } else { + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.fab_import: + startImportConfigFilePicker(); + break; + case R.id.fab_add: + onAddOrDuplicateProfile(null); + break; + } + } private boolean startImportConfigFilePicker() { - boolean startOldFileDialog=true; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) - startOldFileDialog = ! startFilePicker(); + boolean startOldFileDialog = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + startOldFileDialog = !startFilePicker(); if (startOldFileDialog) startImportConfig(); @@ -228,8 +241,8 @@ public class VPNProfileList extends ListFragment { @TargetApi(Build.VERSION_CODES.KITKAT) private boolean startFilePicker() { - Intent i = Utils.getFilePickerIntent(getActivity(), Utils.FileType.OVPN_CONFIG); - if (i!=null) { + Intent i = Utils.getFilePickerIntent(getActivity(), Utils.FileType.OVPN_CONFIG); + if (i != null) { startActivityForResult(i, FILE_PICKER_RESULT_KITKAT); return true; } else @@ -237,32 +250,29 @@ public class VPNProfileList extends ListFragment { } private void startImportConfig() { - Intent intent = new Intent(getActivity(),FileSelect.class); - intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); - intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); - startActivityForResult(intent, SELECT_PROFILE); - } - - - + Intent intent = new Intent(getActivity(), FileSelect.class); + intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); + intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); + startActivityForResult(intent, SELECT_PROFILE); + } - private void onAddOrDuplicateProfile(final VpnProfile mCopyProfile) { - Context context = getActivity(); - if (context != null) { - final EditText entry = new EditText(context); - entry.setSingleLine(); + private void onAddOrDuplicateProfile(final VpnProfile mCopyProfile) { + Context context = getActivity(); + if (context != null) { + final EditText entry = new EditText(context); + entry.setSingleLine(); - AlertDialog.Builder dialog = new AlertDialog.Builder(context); + AlertDialog.Builder dialog = new AlertDialog.Builder(context); if (mCopyProfile == null) - dialog.setTitle(R.string.menu_add_profile); + dialog.setTitle(R.string.menu_add_profile); else { dialog.setTitle(context.getString(R.string.duplicate_profile_title, mCopyProfile.mName)); entry.setText(getString(R.string.copy_of_profile, mCopyProfile.mName)); } dialog.setMessage(R.string.add_profile_name_prompt); - dialog.setView(entry); + dialog.setView(entry); dialog.setNeutralButton(R.string.menu_import_short, new DialogInterface.OnClickListener() { @@ -271,7 +281,7 @@ public class VPNProfileList extends ListFragment { startImportConfigFilePicker(); } }); - dialog.setPositiveButton(android.R.string.ok, + dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -292,32 +302,32 @@ public class VPNProfileList extends ListFragment { }); - dialog.setNegativeButton(android.R.string.cancel, null); - dialog.create().show(); - } + dialog.setNegativeButton(android.R.string.cancel, null); + dialog.create().show(); + } - } + } - private void addProfile(VpnProfile profile) { - getPM().addProfile(profile); - getPM().saveProfileList(getActivity()); - getPM().saveProfile(getActivity(),profile); - mArrayadapter.add(profile); - } + private void addProfile(VpnProfile profile) { + getPM().addProfile(profile); + getPM().saveProfileList(getActivity()); + getPM().saveProfile(getActivity(), profile); + mArrayadapter.add(profile); + } - private ProfileManager getPM() { - return ProfileManager.getInstance(getActivity()); - } + private ProfileManager getPM() { + return ProfileManager.getInstance(getActivity()); + } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); - if(resultCode == RESULT_VPN_DELETED){ - if(mArrayadapter != null && mEditProfile !=null) - mArrayadapter.remove(mEditProfile); - } else if (resultCode == RESULT_VPN_DUPLICATE && data != null) { + if (resultCode == RESULT_VPN_DELETED) { + if (mArrayadapter != null && mEditProfile != null) + mArrayadapter.remove(mEditProfile); + } else if (resultCode == RESULT_VPN_DUPLICATE && data != null) { String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); VpnProfile profile = ProfileManager.get(getActivity(), profileUUID); if (profile != null) @@ -325,37 +335,37 @@ public class VPNProfileList extends ListFragment { } - if(resultCode != Activity.RESULT_OK) - return; + if (resultCode != Activity.RESULT_OK) + return; - if (requestCode == START_VPN_CONFIG) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + if (requestCode == START_VPN_CONFIG) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN); - getPM().saveProfile(getActivity(), profile); - // Name could be modified, reset List adapter - setListAdapter(); + VpnProfile profile = ProfileManager.get(getActivity(), configuredVPN); + getPM().saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + setListAdapter(); - } else if(requestCode== SELECT_PROFILE) { + } else if (requestCode == SELECT_PROFILE) { String fileData = data.getStringExtra(FileSelect.RESULT_DATA); Uri uri = new Uri.Builder().path(fileData).scheme("file").build(); startConfigImport(uri); - } else if(requestCode == IMPORT_PROFILE) { - String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - mArrayadapter.add(ProfileManager.get(getActivity(), profileUUID)); - } else if(requestCode == FILE_PICKER_RESULT_KITKAT) { + } else if (requestCode == IMPORT_PROFILE) { + String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + mArrayadapter.add(ProfileManager.get(getActivity(), profileUUID)); + } else if (requestCode == FILE_PICKER_RESULT_KITKAT) { if (data != null) { Uri uri = data.getData(); startConfigImport(uri); } } - } + } private void startConfigImport(Uri uri) { - Intent startImport = new Intent(getActivity(),ConfigConverter.class); + Intent startImport = new Intent(getActivity(), ConfigConverter.class); startImport.setAction(ConfigConverter.IMPORT_PROFILE); startImport.setData(uri); startActivityForResult(startImport, IMPORT_PROFILE); @@ -363,20 +373,20 @@ public class VPNProfileList extends ListFragment { private void editVPN(VpnProfile profile) { - mEditProfile =profile; - Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) - .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); + mEditProfile = profile; + Intent vprefintent = new Intent(getActivity(), VPNPreferences.class) + .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); - startActivityForResult(vprefintent,START_VPN_CONFIG); - } + startActivityForResult(vprefintent, START_VPN_CONFIG); + } - private void startVPN(VpnProfile profile) { + private void startVPN(VpnProfile profile) { - getPM().saveProfile(getActivity(), profile); + getPM().saveProfile(getActivity(), profile); - Intent intent = new Intent(getActivity(),LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - } + Intent intent = new Intent(getActivity(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } } diff --git a/main/src/main/res/layout-v21/profile_list_fabs.xml b/main/src/main/res/layout-v21/profile_list_fabs.xml index 5ea6512d..28d393f0 100644 --- a/main/src/main/res/layout-v21/profile_list_fabs.xml +++ b/main/src/main/res/layout-v21/profile_list_fabs.xml @@ -9,7 +9,7 @@ tools:ignore="InconsistentLayout"> <ImageButton - android:id="@+id/add_button" + android:id="@+id/fab_import" android:layout_width="@dimen/round_button_diameter" android:layout_height="@dimen/round_button_diameter" android:layout_gravity="end|bottom" @@ -23,7 +23,7 @@ <ImageButton android:contentDescription="@string/add_profile" - android:id="@+id/import_button" + android:id="@+id/fab_add" android:layout_width="@dimen/round_button_diameter" android:layout_height="@dimen/round_button_diameter" android:layout_gravity="end|bottom" diff --git a/main/src/main/res/layout-v21/save_fab.xml b/main/src/main/res/layout-v21/save_fab.xml new file mode 100644 index 00000000..55fdecc6 --- /dev/null +++ b/main/src/main/res/layout-v21/save_fab.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + ~ Copyright (c) 2012-2015 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="InconsistentLayout" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/fab_save" + android:layout_width="@dimen/round_button_diameter" + android:layout_height="@dimen/round_button_diameter" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:layout_marginBottom="16dp" + android:layout_marginEnd="16dp" + android:background="@drawable/oval_ripple" + android:elevation="1dp" + android:src="@android:drawable/ic_menu_save" + android:stateListAnimator="@anim/fab_anim" + android:tint="@android:color/white" + tools:showIn="@layout/config_converter" />
\ No newline at end of file diff --git a/main/src/main/res/layout/config_converter.xml b/main/src/main/res/layout/config_converter.xml index 96381b60..fe4ca31f 100644 --- a/main/src/main/res/layout/config_converter.xml +++ b/main/src/main/res/layout/config_converter.xml @@ -3,10 +3,12 @@ ~ Copyright (c) 2012-2014 Arne Schwabe ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" +<ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/stdpadding" android:orientation="vertical"> @@ -43,6 +45,6 @@ android:id="@+id/textView"/> </LinearLayout> - - -</ScrollView>
\ No newline at end of file + </ScrollView> + <include layout="@layout/save_fab" /> +</FrameLayout> diff --git a/main/src/main/res/layout/save_fab.xml b/main/src/main/res/layout/save_fab.xml new file mode 100644 index 00000000..e3cae9ab --- /dev/null +++ b/main/src/main/res/layout/save_fab.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2014 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + +</merge>
\ No newline at end of file diff --git a/main/src/main/res/layout/vpn_profile_list.xml b/main/src/main/res/layout/vpn_profile_list.xml index 082ffd77..3f295f2d 100644 --- a/main/src/main/res/layout/vpn_profile_list.xml +++ b/main/src/main/res/layout/vpn_profile_list.xml @@ -74,6 +74,6 @@ </LinearLayout> - <!-- <include layout="@layout/profile_list_fabs" /> --> + <include layout="@layout/profile_list_fabs" /> </FrameLayout> diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 88e61928..6f64d2ba 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -380,5 +380,7 @@ <string name="tls_cipher_alert">Newer OpenVPN for Android versions (0.6.29/March 2015) use a more secure default for the allowed cipher suites (tls-cipher \"DEFAULT:!EXP:!PSK:!SRP:!kRSA\"). Unfortunately, omitting the less secure cipher suites and export cipher suites, especially the omission of cipher suites that do not support Perfect Forward Secrecy (Diffie-Hellman) causes some problems. This usually caused by an well-intentioned but poorly executed attempts to strengthen TLS security by setting tls-cipher on the server or some embedded OSes with stripped down SSL (e.g. MikroTik).\nTo solve this problem the problem, set the tls-cipher settings on the server to reasonable default like tls-cipher \"DEFAULT:!EXP:!PSK:!SRP:!kRSA\". To work around the problem on the client add the custom option tls-cipher DEFAULT on the Android client.</string> <string name="message_no_user_edit">This profile has been added from an external app (%s) and has been marked as not user editable.</string> <string name="crl_file">Certificate Revocation List</string> + <string name="service_restarted">Restarting OpenVPN Service (App crashed probably crashed or killed for memory pressure)</string> + <string name="import_config_error">Importing the config yielded an error, cannot save it</string> </resources> |