From 451498b47087f43f71348cc0615b8457d8e312fd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Apr 2017 16:05:00 +0200 Subject: [PATCH 01/43] android: Update Gradle plugin and wrapper --- src/frontends/android/app/build.gradle | 2 +- src/frontends/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 3c1c68a5b..d0ab256bd 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.3" + buildToolsVersion '25.0.0' defaultConfig { applicationId "org.strongswan.android" diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle index 3d5ba798f..e52ed0aca 100644 --- a/src/frontends/android/build.gradle +++ b/src/frontends/android/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.3' } } diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties index a6bd91c6c..8c08a361c 100644 --- a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties +++ b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 20 17:56:35 CEST 2016 +#Tue Apr 18 15:57:06 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip From 61098dd6a301f9c6f2aee6bc59a2785de1712004 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Apr 2017 16:07:55 +0200 Subject: [PATCH 02/43] android: Move Simplified Chinese translation to values-zg-rCN folder --- .../app/src/main/res/{values-zh => values-zh-rCN}/arrays.xml | 0 .../app/src/main/res/{values-zh => values-zh-rCN}/strings.xml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/frontends/android/app/src/main/res/{values-zh => values-zh-rCN}/arrays.xml (100%) rename src/frontends/android/app/src/main/res/{values-zh => values-zh-rCN}/strings.xml (100%) diff --git a/src/frontends/android/app/src/main/res/values-zh/arrays.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml similarity index 100% rename from src/frontends/android/app/src/main/res/values-zh/arrays.xml rename to src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml diff --git a/src/frontends/android/app/src/main/res/values-zh/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml similarity index 100% rename from src/frontends/android/app/src/main/res/values-zh/strings.xml rename to src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml From edad60ea77a873b689ac55d7b8f7ce245a02d600 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Apr 2017 16:14:45 +0200 Subject: [PATCH 03/43] android: Add translation for Traditional Chinese Courtesy of Chris Chiang. --- .../app/src/main/res/values-zh-rTW/arrays.xml | 24 +++ .../src/main/res/values-zh-rTW/strings.xml | 152 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml new file mode 100644 index 000000000..c7a1963bb --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml @@ -0,0 +1,24 @@ + + + + + + IKEv2 EAP (用戶名稱/密碼) + IKEv2 憑證 + IKEv2 憑證 + EAP (用戶名稱/密碼) + IKEv2 EAP-TLS (憑證) + IKEv2 EAP-TNC (用戶名稱/密碼) + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..79eb30114 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,152 @@ + + + + + + strongSwan VPN 用戶端 + strongSwan + 觀看日誌 + 搜尋 + 無法支援VPN + 您的設備無法使用VPN。\n請聯絡供應商。 + 鎖定模式無法連線VPN + 載入中… + 沒有找到設定檔 + strongSwan快速選單 + + + 日誌 + 發送日誌檔 + 沒有日誌檔 + strongSwan %1$s 日誌檔 + + + 沒有設定檔 + 新增VPN設定檔 + 編輯 + 刪除 + 選擇設定檔 + 選擇的設定檔已經刪除 + 沒有選擇設定檔 + 已選擇1個設定檔 + 已選擇%1$d個設定檔 + + + 儲存 + 匯入 + 取消 + 設定檔名稱(選填) + 設定檔名稱 + 預設為已設定的伺服器位置 + 預設為 \"%1$s\" + 伺服器位置 + IP位置或伺服器網域 + VPN類型 + 用戶ID + 密碼(選填) + 可留空白在需要時才請您設定 + 用戶憑證 + 選擇用戶憑證 + 選擇指定的用戶憑證 + 用戶帳號 + 請先選擇一個憑證 + 預設(%1$s) + CA憑證 + 自動選擇 + 選擇CA憑證 + 選擇一個指定的CA憑證 + 進階設定 + 顯示進階設定 + 伺服器ID + 預設為已設定的伺服器位置。自訂值會在授權期間送到伺服器 + 預設為 \"%1$s\"。自訂值會在授權期間送到伺服器 + VPN通道裝置的MTU值 + 如果在某個網路下預設值不適合 + 伺服器Port + 如果和預設值不同,則需要連接的UDP Port + 拆分隧道 + 屏蔽不通过VPN的IPV4流量 + 屏蔽不通过VPN的IPV6流量 + 匯入VPN設定檔 + 匯入VPN設定檔失敗 + 匯入VPN設定檔失敗: %1$s + 沒有找到檔案 + 不明主機 + TLS連線失敗 + 這個VPN設定檔已經存在,當前設定檔會被覆蓋。 + 從VPN設定檔匯入憑證 + \"%1$s\" 對應的憑證 + + 請填寫必要訊息才能初始化連線 + 請輸入您的用戶名稱 + 沒有選擇CA憑證 + 請選擇一項或啟動 自動選擇 + 請輸入一個數字範圍從%1$d到%2$d + EAP-TNC可能會影響您的隱私安全 + 裝置資料已經發送給伺服器管理者 + Trusted Network Connect (TNC) 可以讓伺服器管理者評估用戶裝置的狀況。

在這個目的下,伺服器管理者可能會要求以下資料,例如ID、已安裝的App項目、系統設定、或加密檔案驗證值。

任何資料都只有在驗證伺服器的身分ID之後才會被送出。]]>
+ + + CA憑證 + 沒有憑證 + 重新載入CA憑證 + 系統 + 用戶 + 已經匯入 + 是否刪除憑證? + 憑證將被永久移除! + 導入憑證 + 憑證已經成功匯入 + 憑證匯入失敗 + + + 狀態: + 設定檔: + 結束連線 + 連線中… + 已連線 + 結束連線中… + 無運作中的VPN + 錯誤 + + + 評估詳情: + 受限的 + 失敗的 + 觀看修復說明 + + + 修復說明 + + + 輸入密碼進行連線 + 用戶名稱 + 密碼 + 連線 + 無法建立VPN: + 伺服器位置查詢失敗。 + 伺服器位置無法連線。 + 驗證伺服器授權失敗。 + 用戶授權失敗。 + 穩定性評估失敗。 + 連線中遇到不明錯誤。 + VPN已連線 + 這個VPN設定檔目前已經連線。 + 重新連線 + 是否連線%1$s? + 這將會覆蓋您當前運作的VPN連線! + 連線 + +
From 6766c85231a083a239ac8c7bed6d9ef5644dded2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Apr 2017 17:07:57 +0200 Subject: [PATCH 04/43] android: Add menu item to import VPN profile via Storage Access Framework This is useful in case the proper MIME type was not set for a downloaded profile. --- .../strongswan/android/ui/MainActivity.java | 17 ++++++- .../android/ui/VpnProfileImportActivity.java | 49 ++++++++++++++----- .../android/app/src/main/res/menu/main.xml | 7 ++- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java index 890434255..5ba1061c2 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2017 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.VpnService; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.support.v4.app.Fragment; @@ -140,11 +141,25 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec return true; } + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + { + menu.removeItem(R.id.menu_import_profile); + } + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.menu_import_profile: + Intent intent = new Intent(this, VpnProfileImportActivity.class); + startActivity(intent); + return true; case R.id.menu_manage_certs: Intent certIntent = new Intent(this, TrustedCertificatesActivity.class); startActivity(certIntent); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index 1b6b6e896..8ab1a178d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Tobias Brunner + * Copyright (C) 2016-2017 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; @@ -75,7 +76,9 @@ import javax.net.ssl.SSLHandshakeException; public class VpnProfileImportActivity extends AppCompatActivity { private static final String PKCS12_INSTALLED = "PKCS12_INSTALLED"; + private static final String PROFILE_URI = "PROFILE_URI"; private static final int INSTALL_PKCS12 = 0; + private static final int OPEN_DOCUMENT = 1; private static final int PROFILE_LOADER = 0; private static final int USER_CERT_LOADER = 1; @@ -107,7 +110,7 @@ public class VpnProfileImportActivity extends AppCompatActivity @Override public Loader onCreateLoader(int id, Bundle args) { - return new ProfileLoader(VpnProfileImportActivity.this, getIntent().getData()); + return new ProfileLoader(VpnProfileImportActivity.this, (Uri)args.getParcelable(PROFILE_URI)); } @Override @@ -197,16 +200,13 @@ public class VpnProfileImportActivity extends AppCompatActivity String action = intent.getAction(); if (Intent.ACTION_VIEW.equals(action)) { - mProgress = ProgressDialog.show(this, null, getString(R.string.loading), - true, true, new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) - { - finish(); - } - }); - - getLoaderManager().initLoader(PROFILE_LOADER, null, mProfileLoaderCallbacks); + loadProfile(getIntent().getData()); + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + { + Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openIntent.setType("*/*"); + startActivityForResult(openIntent, OPEN_DOCUMENT); } if (savedInstanceState != null) @@ -279,9 +279,34 @@ public class VpnProfileImportActivity extends AppCompatActivity mImportUserCert.setEnabled(false); mSelectUserCert.performClick(); } + break; + case OPEN_DOCUMENT: + if (resultCode == Activity.RESULT_OK && data != null) + { + loadProfile(data.getData()); + return; + } + finish(); + break; } } + private void loadProfile(Uri uri) + { + mProgress = ProgressDialog.show(this, null, getString(R.string.loading), + true, true, new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) + { + finish(); + } + }); + + Bundle args = new Bundle(); + args.putParcelable(PROFILE_URI, uri); + getLoaderManager().initLoader(PROFILE_LOADER, args, mProfileLoaderCallbacks); + } + public void handleProfile(ProfileLoadResult data) { mProgress.dismiss(); diff --git a/src/frontends/android/app/src/main/res/menu/main.xml b/src/frontends/android/app/src/main/res/menu/main.xml index 599254a5e..3c8da084b 100644 --- a/src/frontends/android/app/src/main/res/menu/main.xml +++ b/src/frontends/android/app/src/main/res/menu/main.xml @@ -1,6 +1,6 @@ + + + + From cea8213f1bbff0cc772e42fa1eb1f04cb290e8a5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 10:14:43 +0200 Subject: [PATCH 35/43] android: Handle checked state in activated background --- .../app/src/main/res/drawable/activated_background.xml | 4 ++++ src/frontends/android/app/src/main/res/values/colors.xml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/frontends/android/app/src/main/res/drawable/activated_background.xml b/src/frontends/android/app/src/main/res/drawable/activated_background.xml index 04cf6effb..457df4fb3 100644 --- a/src/frontends/android/app/src/main/res/drawable/activated_background.xml +++ b/src/frontends/android/app/src/main/res/drawable/activated_background.xml @@ -23,6 +23,10 @@ android:state_activated="true" android:drawable="@color/accent" /> + + diff --git a/src/frontends/android/app/src/main/res/values/colors.xml b/src/frontends/android/app/src/main/res/values/colors.xml index f29ae1911..eedfe2397 100644 --- a/src/frontends/android/app/src/main/res/values/colors.xml +++ b/src/frontends/android/app/src/main/res/values/colors.xml @@ -39,6 +39,9 @@ #5a5a5a + #4a4a4a + #5a5a5a From 5561633fcd54636b1a3f917eb5c86980d0c527b9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 13:50:18 +0200 Subject: [PATCH 36/43] android: Add class that wraps ApplicationInfo for selected apps --- .../ui/adapter/SelectedApplicationEntry.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationEntry.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationEntry.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationEntry.java new file mode 100644 index 000000000..0bfbbcfa5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationEntry.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui.adapter; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; + +import java.text.Collator; + +public class SelectedApplicationEntry implements Comparable +{ + private final ApplicationInfo mInfo; + private final Drawable mIcon; + private final String mName; + private boolean mSelected; + + public SelectedApplicationEntry(PackageManager packageManager, ApplicationInfo info) + { + mInfo = info; + CharSequence name = info.loadLabel(packageManager); + mName = name == null ? info.packageName : name.toString(); + mIcon = info.loadIcon(packageManager); + } + + public void setSelected(boolean selected) + { + mSelected = selected; + } + + public boolean isSelected() + { + return mSelected; + } + + public ApplicationInfo getInfo() + { + return mInfo; + } + + public Drawable getIcon() + { + return mIcon; + } + + @Override + public String toString() + { + return mName; + } + + @Override + public int compareTo(@NonNull SelectedApplicationEntry another) + { + return Collator.getInstance().compare(toString(), another.toString()); + } +} From 238c3061b7b8d674b7ec6053c5468a739b555635 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 13:54:32 +0200 Subject: [PATCH 37/43] android: Add list item layout for installed/selected apps --- .../res/layout/selected_application_item.xml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/frontends/android/app/src/main/res/layout/selected_application_item.xml diff --git a/src/frontends/android/app/src/main/res/layout/selected_application_item.xml b/src/frontends/android/app/src/main/res/layout/selected_application_item.xml new file mode 100644 index 000000000..faece8396 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/selected_application_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + From 208e15c0bab77b609c844c68408550eace537e2d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 14:11:16 +0200 Subject: [PATCH 38/43] android: Add filterable adapter for list of installed/selected apps --- .../adapter/SelectedApplicationsAdapter.java | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java new file mode 100644 index 000000000..2a2fe9533 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui.adapter; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; + +import org.strongswan.android.R; +import org.strongswan.android.ui.widget.CheckableLinearLayout; + +import java.util.ArrayList; +import java.util.List; + +public class SelectedApplicationsAdapter extends BaseAdapter implements Filterable +{ + private Context mContext; + private final Object mLock = new Object(); + private List mData; + private List mDataFiltered; + private SelectedApplicationsFilter mFilter; + + public SelectedApplicationsAdapter(Context context) + { + mContext = context; + mData = mDataFiltered = new ArrayList<>(); + } + + /** + * Set new data for this adapter. + * + * @param data the new data (null to clear) + */ + public void setData(List data) + { + synchronized (mLock) + { + mData.clear(); + mDataFiltered = mData; + if (data != null) + { + mData.addAll(data); + } + } + notifyDataSetChanged(); + } + + @Override + public int getCount() + { + return mDataFiltered.size(); + } + + @Override + public SelectedApplicationEntry getItem(int position) + { + return mDataFiltered.get(position); + } + + @Override + public long getItemId(int position) + { + return mDataFiltered.get(position).toString().hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View view; + if (convertView != null) + { + view = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(mContext); + view = inflater.inflate(R.layout.selected_application_item, parent, false); + } + SelectedApplicationEntry item = getItem(position); + CheckableLinearLayout checkable = (CheckableLinearLayout)view; + checkable.setChecked(item.isSelected()); + ImageView icon = (ImageView)view.findViewById(R.id.app_icon); + icon.setImageDrawable(item.getIcon()); + TextView text = (TextView)view.findViewById(R.id.app_name); + text.setText(item.toString()); + return view; + } + + @Override + public Filter getFilter() + { + if (mFilter == null) + { + mFilter = new SelectedApplicationsFilter(); + } + return mFilter; + } + + private class SelectedApplicationsFilter extends Filter + { + + @Override + protected FilterResults performFiltering(CharSequence constraint) + { + FilterResults results = new FilterResults(); + ArrayList data, filtered; + + synchronized (mLock) + { + data = new ArrayList<>(mData); + } + + if (TextUtils.isEmpty(constraint)) + { + filtered = data; + } + else + { + String filter = constraint.toString().toLowerCase(); + filtered = new ArrayList<>(); + for (SelectedApplicationEntry entry : data) + { + if (entry.toString().toLowerCase().contains(filter)) + { + filtered.add(entry); + } + } + } + results.values = filtered; + results.count = filtered.size(); + return results; + } + + @Override + @SuppressWarnings("unchecked") + protected void publishResults(CharSequence constraint, FilterResults results) + { + mDataFiltered = (List)results.values; + notifyDataSetChanged(); + } + } +} From eb59c6a38a7b0038ea3abc9df39f5a5ed6971206 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 15:10:41 +0200 Subject: [PATCH 39/43] android: Add list fragment for the selection of apps --- .../ui/SelectedApplicationsListFragment.java | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java new file mode 100644 index 000000000..38c7494d5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import android.Manifest; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.util.Pair; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Filter; +import android.widget.ListView; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.ui.adapter.SelectedApplicationEntry; +import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks, List>>, SearchView.OnQueryTextListener +{ + private SelectedApplicationsAdapter mAdapter; + private SortedSet mSelection; + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + mAdapter = new SelectedApplicationsAdapter(getActivity()); + setListAdapter(mAdapter); + setListShown(false); + + ArrayList selection; + if (savedInstanceState == null) + { + selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); + } + else + { + selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); + } + mSelection = new TreeSet<>(selection); + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection)); + } + + /** + * Returns the package names of all selected apps + */ + public ArrayList getSelectedApplications() + { + return new ArrayList<>(mSelection); + } + + /** + * Track check state as ListView is unable to do that when using filters + */ + @Override + public void onListItemClick(ListView l, View v, int position, long id) + { + super.onListItemClick(l, v, position, id); + SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position); + item.setSelected(!item.isSelected()); + if (item.isSelected()) + { + mSelection.add(item.getInfo().packageName); + } + else + { + mSelection.remove(item.getInfo().packageName); + } + } + + /** + * Manually set the check state as ListView is unable to track that when using filters + */ + private void setCheckState() + { + for (int i = 0; i < getListView().getCount(); i++) + { + SelectedApplicationEntry item = mAdapter.getItem(i); + getListView().setItemChecked(i, item.isSelected()); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + MenuItem item = menu.add(R.string.search); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public Loader, List>> onCreateLoader(int id, Bundle args) + { + return new InstalledPackagesLoader(getActivity(), mSelection); + } + + @Override + public void onLoadFinished(Loader, List>> loader, Pair, List> data) + { + mAdapter.setData(data.first); + mSelection.removeAll(data.second); + setCheckState(); + + if (isResumed()) + { + setListShown(true); + } + else + { + setListShownNoAnimation(true); + } + } + + @Override + public void onLoaderReset(Loader, List>> loader) + { + mAdapter.setData(null); + } + + @Override + public boolean onQueryTextSubmit(String query) + { + return true; + } + + @Override + public boolean onQueryTextChange(String newText) + { + String search = TextUtils.isEmpty(newText) ? null : newText; + mAdapter.getFilter().filter(search, new Filter.FilterListener() + { + @Override + public void onFilterComplete(int count) + { + setCheckState(); + } + }); + return true; + } + + public static class InstalledPackagesLoader extends AsyncTaskLoader, List>> + { + private final PackageManager mPackageManager; + private final SortedSet mSelection; + private Pair, List> mData; + + public InstalledPackagesLoader(Context context, SortedSet selection) + { + super(context); + mPackageManager = context.getPackageManager(); + mSelection = selection; + } + + @Override + public Pair, List> loadInBackground() + { + List apps = new ArrayList<>(); + SortedSet seen = new TreeSet<>(); + for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA)) + { + /* skip apps that can't access the network anyway */ + if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED) + { + SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info); + entry.setSelected(mSelection.contains(info.packageName)); + apps.add(entry); + seen.add(info.packageName); + } + } + Collections.sort(apps); + /* check for selected packages that don't exist anymore */ + List missing = new ArrayList<>(); + for (String pkg : mSelection) + { + if (!seen.contains(pkg)) + { + missing.add(pkg); + } + } + return new Pair<>(apps, missing); + } + + @Override + protected void onStartLoading() + { + if (mData != null) + { /* if we have data ready, deliver it directly */ + deliverResult(mData); + } + if (takeContentChanged() || mData == null) + { + forceLoad(); + } + } + + @Override + public void deliverResult(Pair, List> data) + { + if (isReset()) + { + return; + } + mData = data; + if (isStarted()) + { /* if it is started we deliver the data directly, + * otherwise this is handled in onStartLoading */ + super.deliverResult(data); + } + } + + @Override + protected void onReset() + { + mData = null; + super.onReset(); + } + } +} From 3cc6a03fa018e602d1b94c05151a31e4e7fa3124 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 15:13:30 +0200 Subject: [PATCH 40/43] android: Add simple activity for the selection of apps --- .../android/app/src/main/AndroidManifest.xml | 4 + .../ui/SelectedApplicationsActivity.java | 76 +++++++++++++++++++ .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-ua/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + 9 files changed, 87 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml index bc2de9af7..c7f745b70 100644 --- a/src/frontends/android/app/src/main/AndroidManifest.xml +++ b/src/frontends/android/app/src/main/AndroidManifest.xml @@ -48,6 +48,10 @@ android:name=".ui.TrustedCertificatesActivity" android:label="@string/trusted_certs_title" > + + diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java new file mode 100644 index 000000000..db2e56655 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; + +import org.strongswan.android.data.VpnProfileDataSource; + +public class SelectedApplicationsActivity extends AppCompatActivity +{ + private static final String LIST_TAG = "ApplicationList"; + private SelectedApplicationsListFragment mApps; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + + FragmentManager fm = getSupportFragmentManager(); + mApps = (SelectedApplicationsListFragment)fm.findFragmentByTag(LIST_TAG); + if (mApps == null) + { + mApps = new SelectedApplicationsListFragment(); + fm.beginTransaction().add(android.R.id.content, mApps, LIST_TAG).commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + prepareResult(); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() + { + prepareResult(); + super.onBackPressed(); + } + + private void prepareResult() + { + Intent data = new Intent(); + data.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, mApps.getSelectedApplications()); + setResult(RESULT_OK, data); + } +} diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index b981375ff..ca22f78b5 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -87,6 +87,7 @@ Nur Verkehr in die spezifizierten Subnetze wird via VPN geleitet, der Rest wird behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\") Ausgeschlossene Subnetze Verkehr in diese Subnetze wird vom VPN ausgeschlossen und behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\") + Apps auswählen VPN Profile importieren VPN Profil-Import fehlgeschlagen VPN Profil-Import fehlgeschlagen: %1$s diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 6924aecab..1708f6edf 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -87,6 +87,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 0d117b70b..0183e0306 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -84,6 +84,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 22e8da227..156300d3e 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -85,6 +85,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 8df905b82..119d462cc 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -84,6 +84,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications 导入VPN配置 导入VPN配置失败 导入VPN配置失败: %1$s diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index 1aa060587..2b0ce6b2b 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -84,6 +84,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications 匯入VPN設定檔 匯入VPN設定檔失敗 匯入VPN設定檔失敗: %1$s diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 3b35306da..e9a62f03c 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -87,6 +87,7 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Select applications Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s From 291ef58c69d62ad5aed1817ec9ee7ee9ac3b842f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 27 Jun 2017 15:17:43 +0200 Subject: [PATCH 41/43] android: Make app handling and selection of apps configurable in profile editor --- .../android/ui/VpnProfileDetailActivity.java | 98 ++++++++++++++++++- .../main/res/layout/profile_detail_view.xml | 22 +++++ .../app/src/main/res/values-de/arrays.xml | 11 ++- .../app/src/main/res/values-de/strings.xml | 4 + .../app/src/main/res/values-pl/arrays.xml | 7 ++ .../app/src/main/res/values-pl/strings.xml | 4 + .../app/src/main/res/values-ru/arrays.xml | 7 ++ .../app/src/main/res/values-ru/strings.xml | 4 + .../app/src/main/res/values-ua/arrays.xml | 7 ++ .../app/src/main/res/values-ua/strings.xml | 4 + .../app/src/main/res/values-zh-rCN/arrays.xml | 7 ++ .../src/main/res/values-zh-rCN/strings.xml | 4 + .../app/src/main/res/values-zh-rTW/arrays.xml | 7 ++ .../src/main/res/values-zh-rTW/strings.xml | 4 + .../app/src/main/res/values/arrays.xml | 13 ++- .../app/src/main/res/values/strings.xml | 4 + 16 files changed, 200 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index d495b742a..0bae6142d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2016 Tobias Brunner + * Copyright (C) 2012-2017 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * HSR Hochschule fuer Technik Rapperswil @@ -57,6 +57,7 @@ import android.widget.TextView; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfile.SelectedAppsHandling; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.data.VpnType; import org.strongswan.android.data.VpnType.VpnTypeFeature; @@ -68,11 +69,15 @@ import org.strongswan.android.utils.Constants; import org.strongswan.android.utils.IPRangeSet; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.UUID; public class VpnProfileDetailActivity extends AppCompatActivity { private static final int SELECT_TRUSTED_CERTIFICATE = 0; + private static final int SELECT_APPLICATIONS = 1; private VpnProfileDataSource mDataSource; private Long mId; @@ -82,6 +87,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity private String mSelectedUserId; private TrustedCertificateEntry mUserCertEntry; private VpnType mVpnType = VpnType.IKEV2_EAP; + private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE; + private SortedSet mSelectedApps = new TreeSet<>(); private VpnProfile mProfile; private MultiAutoCompleteTextView mName; private TextInputLayoutHelper mNameWrap; @@ -112,6 +119,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity private TextInputLayoutHelper mExcludedSubnetsWrap; private CheckBox mBlockIPv4; private CheckBox mBlockIPv6; + private Spinner mSelectSelectedAppsHandling; + private RelativeLayout mSelectApps; @Override public void onCreate(Bundle savedInstanceState) @@ -161,6 +170,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling); + mSelectApps = (RelativeLayout)findViewById(R.id.select_applications); + final SpaceTokenizer spaceTokenizer = new SpaceTokenizer(); mName.setTokenizer(spaceTokenizer); mRemoteId.setTokenizer(spaceTokenizer); @@ -265,6 +277,32 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); + mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + mSelectedAppsHandling = SelectedAppsHandling.values()[position]; + updateAppsSelector(); + } + + @Override + public void onNothingSelected(AdapterView parent) + { /* should not happen */ + mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE; + updateAppsSelector(); + } + }); + + mSelectApps.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class); + intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps)); + startActivityForResult(intent, SELECT_APPLICATIONS); + } + }); + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); if (mId == null) { @@ -277,6 +315,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity updateCredentialView(); updateCertificateSelector(); updateAdvancedSettings(); + updateAppsSelector(); } @Override @@ -306,6 +345,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity { outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); } + outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps)); } @Override @@ -347,6 +387,14 @@ public class VpnProfileDetailActivity extends AppCompatActivity updateCertificateSelector(); } break; + case SELECT_APPLICATIONS: + if (resultCode == RESULT_OK) + { + ArrayList selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); + mSelectedApps = new TreeSet<>(selection); + updateAppsSelector(); + } + break; default: super.onActivityResult(requestCode, resultCode, data); } @@ -435,6 +483,40 @@ public class VpnProfileDetailActivity extends AppCompatActivity } } + /** + * Update the application selection UI + */ + private void updateAppsSelector() + { + if (mSelectedAppsHandling == SelectedAppsHandling.SELECTED_APPS_DISABLE) + { + mSelectApps.setEnabled(false); + mSelectApps.setVisibility(View.GONE); + + } + else + { + mSelectApps.setEnabled(true); + mSelectApps.setVisibility(View.VISIBLE); + + ((TextView)mSelectApps.findViewById(android.R.id.text1)).setText(R.string.profile_select_apps); + String selected; + switch (mSelectedApps.size()) + { + case 0: + selected = getString(R.string.profile_select_no_apps); + break; + case 1: + selected = getString(R.string.profile_select_one_app); + break; + default: + selected = getString(R.string.profile_select_x_apps, mSelectedApps.size()); + break; + } + ((TextView)mSelectApps.findViewById(android.R.id.text2)).setText(selected); + } + } + /** * Update the advanced settings UI depending on whether any advanced * settings have already been made. @@ -447,7 +529,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity Integer st = mProfile.getSplitTunneling(); show = mProfile.getRemoteId() != null || mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0) || - mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null; + mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null || + mProfile.getSelectedAppsHandling() != SelectedAppsHandling.SELECTED_APPS_DISABLE; } mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); @@ -575,6 +658,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; mProfile.setSplitTunneling(st == 0 ? null : st); + mProfile.setSelectedAppsHandling(mSelectedAppsHandling); + mProfile.setSelectedApps(mSelectedApps); } /** @@ -604,6 +689,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity mExcludedSubnets.setText(mProfile.getExcludedSubnets()); mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0); mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0); + mSelectedAppsHandling = mProfile.getSelectedAppsHandling(); + mSelectedApps = mProfile.getSelectedAppsSet(); useralias = mProfile.getUserCertificateAlias(); local_id = mProfile.getLocalId(); alias = mProfile.getCertificateAlias(); @@ -646,6 +733,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity mCertEntry = null; } } + + mSelectSelectedAppsHandling.setSelection(mSelectedAppsHandling.ordinal()); + if (savedInstanceState != null) + { + ArrayList selectedApps = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); + mSelectedApps = new TreeSet<>(selectedApps); + } } /** diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index f9a512d96..8b153b89a 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -250,6 +250,7 @@ android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:layout_marginLeft="4dp" + android:layout_marginStart="4dp" android:textSize="20sp" android:text="@string/profile_split_tunneling_label" /> @@ -306,6 +307,27 @@ android:layout_height="wrap_content" android:text="@string/profile_split_tunnelingv6_title" /> + + + + + + diff --git a/src/frontends/android/app/src/main/res/values-de/arrays.xml b/src/frontends/android/app/src/main/res/values-de/arrays.xml index d05140165..1eda4f1ae 100644 --- a/src/frontends/android/app/src/main/res/values-de/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-de/arrays.xml @@ -1,7 +1,7 @@ + + Alle Apps verwenden das VPN + Ausgewählte Apps vom VPN ausschliessen + Nur ausgewählte Apps verwenden das VPN + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index ca22f78b5..5c5a22a64 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -87,7 +87,11 @@ Nur Verkehr in die spezifizierten Subnetze wird via VPN geleitet, der Rest wird behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\") Ausgeschlossene Subnetze Verkehr in diese Subnetze wird vom VPN ausgeschlossen und behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\") + Apps Apps auswählen + Keine Apps ausgewählt + Eine App ausgewählt + %1$d Apps ausgewählt VPN Profile importieren VPN Profil-Import fehlgeschlagen VPN Profil-Import fehlgeschlagen: %1$s diff --git a/src/frontends/android/app/src/main/res/values-pl/arrays.xml b/src/frontends/android/app/src/main/res/values-pl/arrays.xml index 30e43f1fb..5df0e72c4 100644 --- a/src/frontends/android/app/src/main/res/values-pl/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-pl/arrays.xml @@ -22,4 +22,11 @@ IKEv2 EAP-TLS (certyfikat) IKEv2 EAP-TNC (użytkownik/hasło) + + + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 1708f6edf..d8965a1e4 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -87,7 +87,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-ru/arrays.xml b/src/frontends/android/app/src/main/res/values-ru/arrays.xml index 5fbd43168..64c234eb4 100644 --- a/src/frontends/android/app/src/main/res/values-ru/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-ru/arrays.xml @@ -21,4 +21,11 @@ IKEv2 EAP-TLS (Сертификат) IKEv2 EAP-TNC (Логин/Пароль) + + + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 0183e0306..7296554d8 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -84,7 +84,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-ua/arrays.xml b/src/frontends/android/app/src/main/res/values-ua/arrays.xml index 1acc0d769..8401ef91f 100644 --- a/src/frontends/android/app/src/main/res/values-ua/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-ua/arrays.xml @@ -21,4 +21,11 @@ IKEv2 EAP-TLS (Сертифікати) IKEv2 EAP-TNC (Логін/Пароль) + + + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 156300d3e..6f30fcf16 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -85,7 +85,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml index 546000ab2..64bd21bbe 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/arrays.xml @@ -21,4 +21,11 @@ IKEv2 EAP-TLS (证书) IKEv2 EAP-TNC (用户名/密码) + + + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 119d462cc..415212eab 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -84,7 +84,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected 导入VPN配置 导入VPN配置失败 导入VPN配置失败: %1$s diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml index c7a1963bb..e4ceccc95 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/arrays.xml @@ -21,4 +21,11 @@ IKEv2 EAP-TLS (憑證) IKEv2 EAP-TNC (用戶名稱/密碼) + + + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index 2b0ce6b2b..e1cdf32e5 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -84,7 +84,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected 匯入VPN設定檔 匯入VPN設定檔失敗 匯入VPN設定檔失敗: %1$s diff --git a/src/frontends/android/app/src/main/res/values/arrays.xml b/src/frontends/android/app/src/main/res/values/arrays.xml index b324b594f..be12aab1c 100644 --- a/src/frontends/android/app/src/main/res/values/arrays.xml +++ b/src/frontends/android/app/src/main/res/values/arrays.xml @@ -1,7 +1,7 @@ + + All applications use the VPN + Exclude selected applications from the VPN + Only selected applications use the VPN + + diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index e9a62f03c..fa38753c2 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -87,7 +87,11 @@ Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") Excluded subnets Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\") + Applications Select applications + No applications selected + One application selected + %1$d applications selected Import VPN profile Failed to import VPN profile Failed to import VPN profile: %1$s From 646260f4641db08daa1574374d0c72490913c091 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 28 Jun 2017 12:33:17 +0200 Subject: [PATCH 42/43] android: Change format of address ranges and print sets --- .../org/strongswan/android/utils/IPRange.java | 2 +- .../org/strongswan/android/utils/IPRangeSet.java | 15 +++++++++++++++ .../strongswan/android/test/IPRangeSetTest.java | 7 +++++++ .../org/strongswan/android/test/IPRangeTest.java | 2 +- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java index 621dfd691..6c0aa659b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRange.java @@ -241,7 +241,7 @@ public class IPRange implements Comparable { return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix; } - return InetAddress.getByAddress(mFrom).getHostAddress() + ".." + + return InetAddress.getByAddress(mFrom).getHostAddress() + "-" + InetAddress.getByAddress(mTo).getHostAddress(); } catch (UnknownHostException ignored) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java index d5f4b619f..4716055bb 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/IPRangeSet.java @@ -205,4 +205,19 @@ public class IPRangeSet implements Iterable { return mRanges.size(); } + + @Override + public String toString() + { /* we could use TextUtils, but that causes the unit tests to fail */ + StringBuilder sb = new StringBuilder(); + for (IPRange range : mRanges) + { + if (sb.length() > 0) + { + sb.append(" "); + } + sb.append(range.toString()); + } + return sb.toString(); + } } diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java index 3b054ceed..af60c15ba 100644 --- a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java +++ b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeSetTest.java @@ -302,4 +302,11 @@ public class IPRangeSetTest assertEquals("next", new IPRange("192.168.1.0/24"), iterator.next()); iterator.remove(); } + + @Test + public void testToString() throws UnknownHostException + { + IPRangeSet set = IPRangeSet.fromString("192.168.3.10/24 192.168.1.0/24 192.168.1.1-192.168.1.10"); + assertEquals("string", "192.168.1.0/24 192.168.3.0/24", set.toString()); + } } diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java index 15d710e5a..51e976e34 100644 --- a/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java +++ b/src/frontends/android/app/src/test/java/org/strongswan/android/test/IPRangeTest.java @@ -200,7 +200,7 @@ public class IPRangeTest { testToString("192.168.1.1", "192.168.1.1", "192.168.1.1/32"); testToString("192.168.1.0", "192.168.1.255", "192.168.1.0/24"); - testToString("192.168.1.1", "192.168.1.255", "192.168.1.1..192.168.1.255"); + testToString("192.168.1.1", "192.168.1.255", "192.168.1.1-192.168.1.255"); testToString("0.0.0.0", "255.255.255.255", "0.0.0.0/0"); testToString("fec1::1", "fec1::1", "fec1:0:0:0:0:0:0:1/128"); testToString("fec1::", "fec1::ffff", "fec1:0:0:0:0:0:0:0/112"); From 0b075420df66cba62287b1609b394253ac67f78e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 28 Jun 2017 14:28:38 +0200 Subject: [PATCH 43/43] android: Use arrays as primary config option for subnets in profile files --- .../android/ui/VpnProfileImportActivity.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index d4c9d32e8..5833258d2 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -527,15 +527,33 @@ public class VpnProfileImportActivity extends AppCompatActivity private String getSubnets(JSONObject split, String key) throws JSONException { - String subnets = split.optString(key, null); - if (subnets != null && !subnets.isEmpty()) + ArrayList subnets = new ArrayList<>(); + JSONArray arr = split.optJSONArray(key); + if (arr != null) { - if (IPRangeSet.fromString(subnets) == null) + for (int i = 0; i < arr.length(); i++) + { /* replace all spaces, e.g. in "192.168.1.1 - 192.168.1.10" */ + subnets.add(arr.getString(i).replace(" ", "")); + } + } + else + { + String value = split.optString(key, null); + if (!TextUtils.isEmpty(value)) + { + subnets.add(value); + } + } + if (subnets.size() > 0) + { + String joined = TextUtils.join(" ", subnets); + IPRangeSet ranges = IPRangeSet.fromString(joined); + if (ranges == null) { throw new JSONException(getString(R.string.profile_import_failed_value, "split-tunneling." + key)); } - return subnets; + return ranges.toString(); } return null; }