From 2ed9ea6dbcd112397a467a0a39f7ca4c0b670cc0 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Mon, 17 Nov 2014 12:31:02 +0100 Subject: Implement setting of allowed apps on VPN on Lollipop --HG-- extra : rebase_source : 4636a99d9a9d8b087c094788f207b18045a4554c --- .../main/java/de/blinkt/openvpn/VpnProfile.java | 17 +- .../blinkt/openvpn/activities/VPNPreferences.java | 12 +- .../java/de/blinkt/openvpn/core/Connection.java | 2 + .../de/blinkt/openvpn/core/OpenVPNService.java | 24 +++ .../openvpn/fragments/Settings_Allowed_Apps.java | 182 +++++++++++++++++++++ main/src/main/res/drawable/white_rect.xml | 3 +- .../main/res/layout/allowed_application_layout.xml | 59 +++++++ main/src/main/res/layout/allowed_vpn_apps.xml | 66 ++++++++ main/src/main/res/values/strings.xml | 8 + main/src/main/res/xml/vpn_headers.xml | 5 + 10 files changed, 369 insertions(+), 9 deletions(-) create mode 100644 main/src/main/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.java create mode 100644 main/src/main/res/layout/allowed_application_layout.xml create mode 100644 main/src/main/res/layout/allowed_vpn_apps.xml diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index aff2a362..f5e55504 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -38,6 +38,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; +import java.util.HashSet; import java.util.Locale; import java.util.UUID; import java.util.Vector; @@ -68,7 +69,7 @@ public class VpnProfile implements Serializable { private static final long serialVersionUID = 7085688938959334563L; public static final int MAXLOGLEVEL = 4; - public static final int CURRENT_PROFILE_VERSION = 4; + public static final int CURRENT_PROFILE_VERSION = 5; public static final int DEFAULT_MSSFIX_SIZE = 1450; public static String DEFAULT_DNS1 = "8.8.8.8"; public static String DEFAULT_DNS2 = "8.8.4.4"; @@ -147,8 +148,11 @@ public class VpnProfile implements Serializable { public String mExcludedRoutes; public String mExcludedRoutesv6; public int mMssFix =0; // -1 is default, - public Connection[] mConnections; + public Connection[] mConnections = new Connection[0]; public boolean mRemoteRandom=false; + public HashSet mAllowedAppsVpn = new HashSet(); + public boolean mAllowedAppsVpnAreDisallowed = true; + /* Options no long used in new profiles */ public String mServerName = "openvpn.blinkt.de"; @@ -208,9 +212,14 @@ public class VpnProfile implements Serializable { mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; } - - if (mProfileVersion < 4) + if (mProfileVersion < 4) { moveOptionsToConnection(); + mAllowedAppsVpnAreDisallowed=true; + } + if (mAllowedAppsVpn==null) + mAllowedAppsVpn = new HashSet(); + if (mConnections ==null) + mConnections = new Connection[0]; mProfileVersion= CURRENT_PROFILE_VERSION; diff --git a/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java b/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java index 1641a51b..d811e029 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java @@ -22,6 +22,7 @@ import android.view.MenuItem; import de.blinkt.openvpn.R; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.fragments.Settings_Allowed_Apps; import de.blinkt.openvpn.fragments.Settings_Authentication; import de.blinkt.openvpn.fragments.Settings_Basic; import de.blinkt.openvpn.fragments.Settings_Behaviour; @@ -38,7 +39,7 @@ public class VPNPreferences extends PreferenceActivity { static final Class validFragments[] = new Class[] { Settings_Authentication.class, Settings_Basic.class, Settings_IP.class, Settings_Obscure.class, Settings_Routing.class, ShowConfigFragment.class, - Settings_Behaviour.class, Settings_Connections.class + Settings_Behaviour.class, Settings_Connections.class, Settings_Allowed_Apps.class }; private String mProfileUUID; @@ -117,12 +118,17 @@ public class VPNPreferences extends PreferenceActivity { @Override public void onBuildHeaders(List
target) { - loadHeadersFromResource(R.xml.vpn_headers, target); - for (Header header : target) { + loadHeadersFromResource(R.xml.vpn_headers, target); + Header headerToRemove=null; + for (Header header : target) { if(header.fragmentArguments==null) header.fragmentArguments = new Bundle(); header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID); + if (header.id == R.id.allowed_apps_header && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + headerToRemove = header; } + if (headerToRemove != null) + target.remove(headerToRemove); } @Override diff --git a/main/src/main/java/de/blinkt/openvpn/core/Connection.java b/main/src/main/java/de/blinkt/openvpn/core/Connection.java index 88cde137..fd5c3761 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/main/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -17,6 +17,8 @@ public class Connection implements Serializable, Cloneable { public boolean mUseCustomConfig=false; public boolean mEnabled=true; + private static final long serialVersionUID = 92031902903829089L; + public String getConnectionBlock() { String cfg=""; 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 29771168..2c40e869 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -14,6 +14,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.VpnService; import android.os.Binder; @@ -558,6 +559,29 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)),TextUtils.join(", ", mRoutesv6.getNetworks(false))); VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + + for (String pkg : mProfile.mAllowedAppsVpn) { + try { + if (mProfile.mAllowedAppsVpnAreDisallowed) { + builder.addDisallowedApplication(pkg); + } else { + builder.addAllowedApplication(pkg); + } + } catch (PackageManager.NameNotFoundException e) { + mProfile.mAllowedAppsVpn.remove(pkg); + VpnStatus.logInfo(R.string.app_no_longer_exists, pkg); + } + } + + if (mProfile.mAllowedAppsVpnAreDisallowed) { + VpnStatus.logDebug(R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); + } else { + VpnStatus.logDebug(R.string.allowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); + } + + } + String session = mProfile.mName; if (mLocalIP != null && mLocalIPv6 != null) diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.java b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.java new file mode 100644 index 00000000..3da48f2b --- /dev/null +++ b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2012-2014 Arne Schwabe + * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.fragments; + +import android.app.Fragment; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.Filter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RadioGroup; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.ToggleButton; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; + +/** + * Created by arne on 16.11.14. + */ +public class Settings_Allowed_Apps extends Fragment { + private ListView mListView; + private VpnProfile mProfile; + + static class AppViewHolder { + public ApplicationInfo mInfo; + public View rootView; + public TextView appName; + public ImageView appIcon; + public TextView appSize; + public TextView disabled; + public CheckBox checkBox; + + static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.allowed_application_layout, null); + + // Creates a ViewHolder and store references to the two children views + // we want to bind data to. + AppViewHolder holder = new AppViewHolder(); + holder.rootView = convertView; + holder.appName = (TextView) convertView.findViewById(R.id.app_name); + holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); + holder.appSize = (TextView) convertView.findViewById(R.id.app_size); + holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); + holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_selected); + convertView.setTag(holder); + return holder; + } else { + // Get the ViewHolder back to get fast access to the TextView + // and the ImageView. + return (AppViewHolder)convertView.getTag(); + } + } + + } + + + static class PackageAdapter extends BaseAdapter { + private final List mPackages; + private final LayoutInflater mInflater; + private final PackageManager mPm; + private final VpnProfile mProfile; + + PackageAdapter(Context c, VpnProfile vp) { + mPm = c.getPackageManager(); + mProfile = vp; + mPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA); + mInflater = LayoutInflater.from(c); + + Collections.sort(mPackages, new ApplicationInfo.DisplayNameComparator(mPm)); + + } + @Override + public int getCount() { + return mPackages.size(); + } + + @Override + public Object getItem(int position) { + return mPackages.get(position); + } + + @Override + public long getItemId(int position) { + return mPackages.get(position).packageName.hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + AppViewHolder viewHolder = AppViewHolder.createOrRecycle(mInflater, convertView); + convertView = viewHolder.rootView; + viewHolder.mInfo = mPackages.get(position); + final ApplicationInfo mInfo = mPackages.get(position); + + + CharSequence appName = mInfo.loadLabel(mPm); + + if (TextUtils.isEmpty(appName)) + appName = mInfo.packageName; + viewHolder.appName.setText(appName); + viewHolder.appIcon.setImageDrawable(mInfo.loadIcon(mPm)); + + + viewHolder.checkBox.setChecked(mProfile.mAllowedAppsVpn.contains(mInfo.packageName)); + viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) + mProfile.mAllowedAppsVpn.remove(mInfo.packageName); + else + mProfile.mAllowedAppsVpn.add(mInfo.packageName); + } + }); + return viewHolder.rootView; + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String profileUuid = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile= ProfileManager.get(getActivity(), profileUuid); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.allowed_vpn_apps, container, false); + RadioGroup group = (RadioGroup) v.findViewById(R.id.allowed_vpn_radiogroup); + + group.check(mProfile.mAllowedAppsVpnAreDisallowed ? R.id.radio_vpn_disallow : R.id.radio_vpn_allow); + + group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch(checkedId){ + case R.id.radio_vpn_allow: + mProfile.mAllowedAppsVpnAreDisallowed=false; + break; + case R.id.radio_vpn_disallow: + mProfile.mAllowedAppsVpnAreDisallowed=true; + break; + } + } + }); + + mListView = (ListView) v.findViewById(android.R.id.list); + + mListView.setAdapter(new PackageAdapter(getActivity(), mProfile)); + + return v; + } +} diff --git a/main/src/main/res/drawable/white_rect.xml b/main/src/main/res/drawable/white_rect.xml index bb5a9fba..da286e5b 100644 --- a/main/src/main/res/drawable/white_rect.xml +++ b/main/src/main/res/drawable/white_rect.xml @@ -5,7 +5,6 @@ --> - - + \ No newline at end of file diff --git a/main/src/main/res/layout/allowed_application_layout.xml b/main/src/main/res/layout/allowed_application_layout.xml new file mode 100644 index 00000000..fddfe22c --- /dev/null +++ b/main/src/main/res/layout/allowed_application_layout.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/main/src/main/res/layout/allowed_vpn_apps.xml b/main/src/main/res/layout/allowed_vpn_apps.xml new file mode 100644 index 00000000..e5228f8a --- /dev/null +++ b/main/src/main/res/layout/allowed_vpn_apps.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 964ce2ae..4a76ac70 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -328,5 +328,13 @@ Set MSS of TCP payload Client behaviour Clear allowed external apps + Loading… + Allow only these applications to use the VPN. Other apps will use the normal connection. + Do not allow these applications to use the VPN. Other apps will use the VPN connection. + Allowed VPN apps: %1$s + Disallowed VPN apps: %1$s + Package %s is no longer installed, removing it from app allow/disallow list + VPN is used for all apps but exclude selected + VPN is used for only for selected apps diff --git a/main/src/main/res/xml/vpn_headers.xml b/main/src/main/res/xml/vpn_headers.xml index 576aea8b..18b78c09 100644 --- a/main/src/main/res/xml/vpn_headers.xml +++ b/main/src/main/res/xml/vpn_headers.xml @@ -28,6 +28,11 @@ android:fragment="de.blinkt.openvpn.fragments.Settings_Authentication" android:title="@string/settings_auth" /> +