diff options
author | Arne Schwabe <arne@rfc2549.org> | 2014-11-17 12:31:02 +0100 |
---|---|---|
committer | Arne Schwabe <arne@rfc2549.org> | 2014-11-17 12:31:02 +0100 |
commit | 2ed9ea6dbcd112397a467a0a39f7ca4c0b670cc0 (patch) | |
tree | 609bcc9bcef4ef50394efd594a38c398a5cbeb17 | |
parent | f4a711c94201c1dc0fc1f38c5513c8c52056f078 (diff) |
Implement setting of allowed apps on VPN on Lollipop
--HG--
extra : rebase_source : 4636a99d9a9d8b087c094788f207b18045a4554c
10 files changed, 369 insertions, 9 deletions
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<String> mAllowedAppsVpn = new HashSet<String>(); + 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<String>(); + 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<Header> 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<ApplicationInfo> 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 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="#42000000" /> - <corners android:radius="5dp" /> + <solid android:color="@android:color/background_light" /> </shape>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2014 Arne Schwabe + ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + --> + +<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingTop="8dip" + android:paddingBottom="8dip" + android:columnCount="4"> + + <ImageView + android:id="@+id/app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:layout_rowSpan="2" + android:layout_marginEnd="8dip" + android:scaleType="centerInside" + tools:drawable="@drawable/icon" + android:contentDescription="@null" /> + + <TextView + tools:text="@string/app" + android:id="@+id/app_name" + android:layout_width="0dip" + android:layout_columnSpan="2" + android:layout_gravity="fill_horizontal" + android:layout_marginTop="2dip" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textAlignment="viewStart" /> + + <CheckBox android:id="@+id/app_selected" + android:layout_marginStart="8dip" + android:layout_gravity="center_vertical" + android:layout_rowSpan="2" + android:visibility="visible" /> + + <TextView + android:id="@+id/app_size" + android:layout_width="0dip" + android:layout_gravity="fill_horizontal|top" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="viewStart" /> + + <TextView + android:id="@+id/app_disabled" + android:layout_marginStart="8dip" + android:layout_gravity="top" + android:textAppearance="?android:attr/textAppearanceSmall" /> + +</GridLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (c) 2012-2014 Arne Schwabe + ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <RadioGroup + android:id="@+id/allowed_vpn_radiogroup" + android:layout_width="match_parent" + android:elevation="2dp" + android:background="@drawable/white_rect" + android:layout_height="wrap_content"> + + <RadioButton + android:layout_margin="@dimen/stdpadding" + android:id="@+id/radio_vpn_disallow" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/vpn_disallow_radio" /> + + <RadioButton + android:layout_margin="@dimen/stdpadding" + android:id="@+id/radio_vpn_allow" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/vpn_allow_radio" /> + </RadioGroup> + + <ListView + android:id="@android:id/list" + android:drawSelectorOnTop="false" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:scrollbarStyle="outsideOverlay" /> + + + <LinearLayout + android:id="@+id/loading_container" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="visible" + android:gravity="center"> + + <ProgressBar + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/loading" + android:paddingTop="4dip" + android:singleLine="true" /> + + </LinearLayout> + + +</LinearLayout>
\ 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 @@ <string name="mssfix_dialogtitle">Set MSS of TCP payload</string> <string name="client_behaviour">Client behaviour</string> <string name="clear_external_apps">Clear allowed external apps</string> + <string name="loading">Loading…</string> + <string name="vpn_allow_mode_is_allow">Allow only these applications to use the VPN. Other apps will use the normal connection.</string> + <string name="vpn_allow_mode_is_disallow">Do not allow these applications to use the VPN. Other apps will use the VPN connection.</string> + <string name="allowed_vpn_apps_info">Allowed VPN apps: %1$s</string> + <string name="disallowed_vpn_apps_info">Disallowed VPN apps: %1$s</string> + <string name="app_no_longer_exists">Package %s is no longer installed, removing it from app allow/disallow list</string> + <string name="vpn_disallow_radio">VPN is used for all apps but exclude selected</string> + <string name="vpn_allow_radio">VPN is used for only for selected apps</string> </resources> 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" /> + <header + android:fragment="de.blinkt.openvpn.fragments.Settings_Allowed_Apps" + android:title="Allowed Apps on VPN" + android:id="@+id/allowed_apps_header" + /> <!-- android:icon="@drawable/ic_settings_display" --> <header android:fragment="de.blinkt.openvpn.fragments.Settings_Obscure" |