summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2014-11-17 12:31:02 +0100
committerArne Schwabe <arne@rfc2549.org>2014-11-17 12:31:02 +0100
commit2ed9ea6dbcd112397a467a0a39f7ca4c0b670cc0 (patch)
tree609bcc9bcef4ef50394efd594a38c398a5cbeb17
parentf4a711c94201c1dc0fc1f38c5513c8c52056f078 (diff)
Implement setting of allowed apps on VPN on Lollipop
--HG-- extra : rebase_source : 4636a99d9a9d8b087c094788f207b18045a4554c
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java17
-rw-r--r--main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java12
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/Connection.java2
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java24
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/Settings_Allowed_Apps.java182
-rw-r--r--main/src/main/res/drawable/white_rect.xml3
-rw-r--r--main/src/main/res/layout/allowed_application_layout.xml59
-rw-r--r--main/src/main/res/layout/allowed_vpn_apps.xml66
-rwxr-xr-xmain/src/main/res/values/strings.xml8
-rw-r--r--main/src/main/res/xml/vpn_headers.xml5
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"