diff options
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" | 
