diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index a307a61..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,37 +0,0 @@
-# Built application files
-/*/build/
-
-# Crashlytics configuations
-com_crashlytics_export_strings.xml
-
-# Local configuration file (sdk path, etc)
-local.properties
-
-# Gradle generated files
-.gradle/
-
-# Signing files
-.signing/
-
-# User-specific configurations
-.idea/libraries/
-.idea/workspace.xml
-.idea/tasks.xml
-.idea/.name
-.idea/compiler.xml
-.idea/copyright/profiles_settings.xml
-.idea/encodings.xml
-.idea/misc.xml
-.idea/modules.xml
-.idea/scopes/scope_settings.xml
-.idea/vcs.xml
-*.iml
-
-# OS-specific files
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-ehthumbs.db
-Thumbs.db
\ No newline at end of file
diff --git a/Readme-ja.md b/Readme-ja.md
new file mode 100644
index 0000000..bf4d697
--- /dev/null
+++ b/Readme-ja.md
@@ -0,0 +1,141 @@
+Android 通信 Proxy 設定ツール
+=============
+
+Language/[English](https://github.com/raise-isayan/TunProxy/blob/master/Readme.md)
+
+このツールは、Android の VPNService 機能を利用した Proxy 設定ツールです。
+指定したアプリからの通信のみを取得することが可能です。
+
+## 使用方法
+
+ユーザ証明書領域に信頼させたい Root CA がない場合はインストールします。
+
+TunProxyアプリを起動すると以下の画面が起動します。
+
+
+
+* Proxy address (ipv4:port)
+ * 接続先のプロキシサーバを **IPv4アドレス:ポート番号** の形式で指定します。
+ IPアドレスはIPv4形式で記載する必要があります。
+
+* [Start] ボタン
+ * 接続を開始します。
+* [Stop] ボタン
+ * 接続を停止します。
+
+## メニュー
+
+画面上部のメニューアイコン()からアプリケーションの設定ができます。
+
+### Settings
+
+VPNの接続設定を行います。
+
+ ⇒ 
+
+Disallowed Application と Allowed Application の2つのモードがありますが、同時に指定することはできません。
+このためどちらのモードで動作させたいかを選択する必要があります。
+デフォルトは **Disallowed Application** が選択された状態です。
+
+* Disallowed Application
+ * VPN通信から除外したいアプリを選択する。
+ 選択したアプリはVPN通信を経由されなくなり、VPNを利用しない場合と同じ挙動となります。
+
+* Allowed Application
+ * VPN通信を行いたいアプリを選択する。
+ 選択したアプリはVPN通信を経由するようになります。
+ 選択されていないアプリは、VPNを利用しない場合と同じ挙動になります。
+ なお、一つも選択されていない場合は、すべてのアプリの通信がVPNを経由します。
+
+* Clear all selection
+ * Allowed / Disallowed Application のすべての選択をクリアします。
+
+### Settings 検索
+
+ / 
+
+画面上部の検索アイコン()から、アプリケーションを絞り込めます。
+アプリケーション名または、パッケージ名に指定したキーワードを含むアプリケーションのみが表示されます。
+
+プリケーションリストは、画面上部のメニューアイコン()からソートできます。
+
+### Settings Menu
+
+アプリ一覧の表示方法を変更します。
+
+* show system app
+ * システムアプリを表示します。
+
+#### sort by
+
+* app name
+ * アプリケーション名でアプリケーションリストを並べ替えます。
+
+* package name
+ * パッケージ名でアプリケーションリストを並べ替えます。
+
+#### order by
+
+* ascending
+ * 昇順にソートします
+
+* descending
+ * 降順にソートします
+
+#### filter by
+
+* app name
+ * アプリケーション名に指定したキーワードを含むものを検索します。
+
+* package name
+ * パッケージ名に指定したキーワードを含むものを検索します。
+
+### MITM (SSL 復号化)
+
+TunProxyはSSL復号化を実行しません。TunProxyは透過プロキシのように機能します。
+
+SSL復号化を実行するには、Burp suite や Fiddler などのSSLを復号化可能なローカルプロキシツールのIPをTunProxyのIPに設定します
+
+SSLを復号化可能なローカルプロキシツールとしては次に記載するものがあります。
+
+* Burp suite
+ * https://portswigger.net/burp
+
+* Fiddler
+ * https://www.telerik.com/fiddler
+
+* ZAP Proxy
+ * https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
+
+
+SSLを復号化するには、ローカルプロキシツールのRoot証明書をAndroid端末のユーザ証明書にインストールしてください。
+ただし、Android 7.0 以降において、デフォルトではユーザ証明書を信頼しなくなっています。
+
+* https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html
+
+解決策として次のWebサイトを参照してください。
+
+* Android 7 Nougatおよび認証局
+ * https://blog.jeroenhd.nl/article/android-7-nougat-and-certificate-authorities
+
+### About
+アプリケーションバージョンを表示します。
+
+## 動作環境
+
+* Android 5.0 (API Level 21) 以降
+
+### ビルド
+ gradlew build
+
+## 謝辞
+
+アプリ作成にあたりコードの大部分は以下のアプリをベースとして作成しました。
+
+* forked from MengAndy/tun2http
+ * https://github.com/MengAndy/tun2http/
+
+## 開発環境
+
+* JRE(JDK) 1.8以上(Open JDK)
+* AndroidStudio 2021.1.1 (https://developer.android.com/studio/index.html)
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..a31d8c4
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,137 @@
+Android HTTP traffic Proxy setting tool
+=============
+
+Language/[Japanese](https://github.com/raise-isayan/TunProxy/blob/master/Readme-ja.md)
+
+This tool is a proxy configuration tool that takes advantage of Android VPNService feature.
+Only the communication from the specified application can be acquired.
+
+## how to use
+
+When you start the TunProxy application, the following screen will be launched.
+
+
+
+* Proxy address (ipv4:port)
+ * Specify the destination proxy server in the format **IPv4 address:port number**.
+ The IP address must be described in IPv4 format.
+
+* [Start] button
+ * Start the VPN service.
+* [Stop] button
+ * Stop the VPN service.
+
+## menu
+
+Application settings can be made from the menu icon () at the top of the screen.
+
+### Settings
+
+Configure VPN service settings.
+
+ ⇒ 
+
+There are two modes, Disallowed Application and Allowed Application, but you can not specify them at the same time.
+Because of this you will have to choose whether you want to run in either mode.
+The default is **Disallowed Application** selected.
+
+* Disallowed Application
+ * Select the application you want to exclude from VPN service.
+ The selected application will no longer go through VPN service and behave the same as if you do not use VPN.
+
+* Allowed Application
+ * Select the application for which you want to perform VPN service.
+ The selected application will now go through VPN service.
+ Applications that are not selected behave the same as when not using VPN.
+ In addition, if none of them are selected, communication of all applications will go through VPN.
+
+* Clear all selection
+ * Clear all selections of Allowed / Disallowed application list.
+
+### Settings Search
+
+ / 
+
+You can narrow down the applications from the search icon.()
+Only applications that contain the keyword specified in the application name or package name will be displayed.
+
+The application list can be sorted from the menu icon () at the top of the screen.
+
+### Settings Menu
+
+Changed the way the application list is displayed.
+
+* show system app
+ * show system application
+
+### sort by
+
+* app name
+ * Sort application list by application name
+
+* package name
+ * Sort application list by package name
+
+### order by
+
+* ascending
+ * Sorting in ascending order
+
+* descending
+ * Sorting in descending order
+
+### filter by
+
+* app name
+ * Search for the application name that contains the keyword you specified.
+
+* package name
+ * Search for the package name that contains the keyword you specified.
+
+### MITM (SSL decrypt)
+
+TunProxy does not perform SSL decryption. TunProxy acts like a transparent proxy.
+To perform SSL decryption, set the IP of an SSL decryptable proxy such as Burp suite or Fiddler to the IP of TunProxy
+
+The following are local proxy tools that can decrypt SSL.
+
+* Burp suite
+ * https://portswigger.net/burp
+
+* Fiddler
+ * https://www.telerik.com/fiddler
+
+* ZAP Proxy
+ * https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
+
+To decrypt SSL, install the local proxy tool Root certificate in the Android device user certificate.
+However, in Android 7.0 and later, the application no longer trusts user certificates by default.
+
+* https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html
+
+Please refer to the following web site as a solution
+
+* Android 7 Nougat and certificate authorities
+ * https://blog.jeroenhd.nl/article/android-7-nougat-and-certificate-authorities
+
+### About
+Display application version
+
+## Operating environment
+
+* Android 5.0 (API Level 21) or later
+
+### ビルド
+ gradlew build
+
+## base application
+
+Most of the code was created based on the following applications for creating applications.
+
+* forked from MengAndy/tun2http
+ * https://github.com/MengAndy/tun2http/
+
+## Development environment
+
+* JRE(JDK) 1.8 or later(Open JDK)
+* AndroidStudio 2021.1.1 (https://developer.android.com/studio/index.html)
diff --git a/android_app/.gitignore b/android_app/.gitignore
new file mode 100644
index 0000000..cbc9ec9
--- /dev/null
+++ b/android_app/.gitignore
@@ -0,0 +1,7 @@
+*.iml
+.gradle
+.idea
+.DS_Store
+local.properties
+/build
+*.zip
diff --git a/android_app/app/.gitignore b/android_app/app/.gitignore
index 796b96d..e3b47b6 100644
--- a/android_app/app/.gitignore
+++ b/android_app/app/.gitignore
@@ -1 +1,3 @@
/build
+.cxx
+.externalNativeBuild
diff --git a/android_app/app/CMakeLists.txt b/android_app/app/CMakeLists.txt
index 8f126c2..dd6a6aa 100644
--- a/android_app/app/CMakeLists.txt
+++ b/android_app/app/CMakeLists.txt
@@ -1,35 +1,35 @@
-cmake_minimum_required(VERSION 3.4.1)
+cmake_minimum_required(VERSION 3.10.2)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp)
-set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}")
+set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}")
add_library( # Sets the name of the library.
- tun2http
+ tun2http
- # Sets the library as a shared library.
- SHARED
+ # Sets the library as a shared library.
+ SHARED
- src/main/cpp/dhcp.c
- src/main/cpp/dns.c
- src/main/cpp/icmp.c
- src/main/cpp/ip.c
- src/main/cpp/http.c
- src/main/cpp/tun2http.c
- src/main/cpp/session.c
- src/main/cpp/tcp.c
- src/main/cpp/tls.c
- src/main/cpp/udp.c
- src/main/cpp/util.c
-)
+ src/main/cpp/dhcp.c
+ src/main/cpp/dns.c
+ src/main/cpp/icmp.c
+ src/main/cpp/ip.c
+ src/main/cpp/http.c
+ src/main/cpp/tun2http.c
+ src/main/cpp/session.c
+ src/main/cpp/tcp.c
+ src/main/cpp/tls.c
+ src/main/cpp/udp.c
+ src/main/cpp/util.c
+ )
find_library( # Sets the name of the path variable.
- log-lib
+ log-lib
- # Specifies the name of the NDK library that
- # you want CMake to locate.
- log )
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ log)
target_link_libraries( # Specifies the target library.
- tun2http
- ${log-lib}
- )
\ No newline at end of file
+ tun2http
+ ${log-lib}
+ )
\ No newline at end of file
diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle
index 46bd3d7..fc60e5f 100644
--- a/android_app/app/build.gradle
+++ b/android_app/app/build.gradle
@@ -1,38 +1,84 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 26
+ compileSdkVersion 31
+ buildToolsVersion "31.0.0"
+
+ sourceCompatibility = '1.8' // -source
+ targetCompatibility = '1.8' // -target
+
defaultConfig {
- applicationId "com.tun2http.app"
- minSdkVersion 19
- targetSdkVersion 26
- versionCode 2
- versionName '1.01'
+ applicationId "tun.proxy"
+ minSdkVersion 21
+ targetSdkVersion 31
+ versionCode 100270
+ versionName VERSION_NAME
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
- cppFlags "-std=c++11 -fvisibility=hidden "
- abiFilters 'armeabi-v7a'
+ cppFlags "-std=c++17 -fvisibility=hidden "
+ abiFilters 'armeabi-v7a','arm64-v8a', 'x86'
arguments "-DCMAKE_VERBOSE_MAKEFILE=1 -DANDROID_FUNCTION_LEVEL_LINKING=ON"
}
}
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ signingConfigs {
+ debug {
+ }
+ release {
+ // @See gradle.properties
+ storeFile file(productKeyStore)
+ keyAlias productKeyAlias
+ storePassword productKeyStorePassword
+ keyPassword productKeyAliasPassword
+ }
}
buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
}
}
+ lintOptions {
+ abortOnError false
+ }
externalNativeBuild {
cmake {
- path "CMakeLists.txt"
+ path file('CMakeLists.txt')
}
}
- productFlavors {
+
+ ndkVersion = '22.1.7171670'
+
+ applicationVariants.all { variant ->
+ if (variant.buildType.name.equals("release")) {
+ variant.outputs.all {
+ if (outputFileName != null && outputFileName.endsWith('.apk')) {
+ def versionName = defaultConfig.versionName
+ outputFileName = "${APP_NAME}_v${versionName}.apk"
+ }
+ }
+ }
}
+
}
dependencies {
- implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation 'com.android.support:appcompat-v7:26.1.0'
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.preference:preference:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.vectordrawable:vectordrawable:1.0.0'
+ implementation 'androidx.legacy:legacy-support-v13:1.0.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.navigation:navigation-fragment:2.0.0'
+ implementation 'androidx.navigation:navigation-ui:2.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
diff --git a/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java b/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java
deleted file mode 100644
index 9de396e..0000000
--- a/android_app/app/src/androidTest/java/com/tun2http/app/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.tun2http.app;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("com.tun2http.app", appContext.getPackageName());
- }
-}
diff --git a/android_app/app/src/androidTest/java/tun/proxy/AppInstrumentedTest.java b/android_app/app/src/androidTest/java/tun/proxy/AppInstrumentedTest.java
new file mode 100644
index 0000000..676027b
--- /dev/null
+++ b/android_app/app/src/androidTest/java/tun/proxy/AppInstrumentedTest.java
@@ -0,0 +1,50 @@
+package tun.proxy;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import tun.utils.Util;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppInstrumentedTest {
+
+ @Before
+ public void setUp() {
+ System.loadLibrary("tun2http");
+ }
+
+ @After
+ public void tearDown() {
+
+ }
+
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("tun.proxy", appContext.getPackageName());
+
+ List dnsList = Util.getDefaultDNS(appContext);
+ System.out.println("dnsList:" + dnsList.size());
+ for (String dns: dnsList) {
+ System.out.println("dns:" + dns);
+ }
+
+ }
+}
diff --git a/android_app/app/src/androidTest/java/tun/proxy/ProgressTaskTest.java b/android_app/app/src/androidTest/java/tun/proxy/ProgressTaskTest.java
new file mode 100644
index 0000000..2bc4d4d
--- /dev/null
+++ b/android_app/app/src/androidTest/java/tun/proxy/ProgressTaskTest.java
@@ -0,0 +1,66 @@
+package tun.proxy;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import tun.utils.ProgressTask;
+import tun.utils.Util;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public class ProgressTaskTest {
+ private static final String TAG = "ProgressTaskTest";
+
+ @Before
+ public void setUp() {
+
+ }
+
+ @After
+ public void tearDown() {
+
+ }
+
+ @Test
+ public void progressTask() {
+ Log.w(TAG, "progressTask: start");
+
+ ProgressTask task = new ProgressTask>() {
+
+ @Override
+ protected List doInBackground(String... var1) {
+ for (int i = 0; i < 100; i++) {
+ try {
+ Thread.sleep(10);
+ Log.d(TAG, "Progress:" + i);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ };
+
+ Assert.assertEquals(task.getStatus(), ProgressTask.Status.PENDING);
+ Log.w(TAG, "progressTask: execute");
+ task.execute();
+ Assert.assertEquals(task.getStatus(), ProgressTask.Status.RUNNING);
+
+ Log.w(TAG, "progressTask: end");
+
+ }
+}
diff --git a/android_app/app/src/main/AndroidManifest.xml b/android_app/app/src/main/AndroidManifest.xml
index dbef8e4..2127f48 100644
--- a/android_app/app/src/main/AndroidManifest.xml
+++ b/android_app/app/src/main/AndroidManifest.xml
@@ -1,40 +1,61 @@
-
-
-
-
+ package="tun.proxy">
+
+
+
+
+
+
+
+
-
-
+
-
-
-
+
+
+
+
+
+
+ android:permission="android.permission.BIND_VPN_SERVICE"
+ android:exported="true">
-
+
+
+ android:label="@string/app_name"
+ android:exported="true">
+
\ No newline at end of file
diff --git a/android_app/app/src/main/cpp/http.c b/android_app/app/src/main/cpp/http.c
index 08111d7..5c028ba 100644
--- a/android_app/app/src/main/cpp/http.c
+++ b/android_app/app/src/main/cpp/http.c
@@ -34,8 +34,7 @@ static const char http_503[] =
#include "tun2http.h"
-int
-get_header(const char *header, const char *data, size_t data_len, char *value) {
+int get_header(const char *header, const char *data, size_t data_len, char *value) {
int len, header_len;
header_len = strlen(header);
@@ -64,8 +63,7 @@ get_header(const char *header, const char *data, size_t data_len, char *value) {
return -2;
}
-int
-next_header(const char **data, size_t *len) {
+int next_header(const char **data, size_t *len) {
int header_len;
/* perhaps we can optimize this to reuse the value of header_len, rather
@@ -139,20 +137,42 @@ uint8_t *patch_http_url(uint8_t *data, size_t *data_len) {
//GET POST PUT DELETE HEAD OPTIONS PATCH
char *word;
uint8_t *pos = 0;
- if (pos = find_data(data, *data_len, "GET ")) {
+ if ((pos = find_data(data, *data_len, "GET ")) > 0) {
word = "GET ";
- } else if (pos = find_data(data, *data_len, "POST ")) {
+ } else if ((pos = find_data(data, *data_len, "POST ")) > 0) {
word = "POST ";
- } else if (pos = find_data(data, *data_len, "PUT ")) {
+ } else if ((pos = find_data(data, *data_len, "PUT ")) > 0) {
word = "PUT ";
- } else if (pos = find_data(data, *data_len, "DELETE ")) {
+ } else if ((pos = find_data(data, *data_len, "DELETE ")) > 0) {
word = "DELETE ";
- } else if (pos = find_data(data, *data_len, "HEAD ")) {
+ } else if ((pos = find_data(data, *data_len, "HEAD ")) > 0) {
word = "HEAD ";
- } else if (pos = find_data(data, *data_len, "OPTIONS ")) {
+ } else if ((pos = find_data(data, *data_len, "OPTIONS ")) > 0) {
word = "OPTIONS ";
- } else if (pos = find_data(data, *data_len, "PATCH ")) {
+ } else if ((pos = find_data(data, *data_len, "PATCH ")) > 0) {
word = "PATCH ";
+ } else if ((pos = find_data(data, *data_len, "HEAD ")) > 0) {
+ word = "HEAD ";
+ } else if ((pos = find_data(data, *data_len, "TRACE ")) > 0) {
+ word = "TRACE ";
+ } else if ((pos = find_data(data, *data_len, "PROPFIND ")) > 0) {
+ word = "PROPFIND ";
+ } else if ((pos = find_data(data, *data_len, "PROPPATCH ")) > 0) {
+ word = "PROPPATCH ";
+ } else if ((pos = find_data(data, *data_len, "MKCOL ")) > 0) {
+ word = "MKCOL ";
+ } else if ((pos = find_data(data, *data_len, "COPY ")) > 0) {
+ word = "COPY ";
+ } else if ((pos = find_data(data, *data_len, "MOVE ")) > 0) {
+ word = "MOVE ";
+ } else if ((pos = find_data(data, *data_len, "LOCK ")) > 0) {
+ word = "LOCK ";
+ } else if ((pos = find_data(data, *data_len, "UNLOCK ")) > 0) {
+ word = "UNLOCK ";
+ } else if ((pos = find_data(data, *data_len, "LINK ")) > 0) {
+ word = "LINK ";
+ } else if ((pos = find_data(data, *data_len, "UNLINK "))> 0) {
+ word = "UNLINK ";
}
if (!pos) {
diff --git a/android_app/app/src/main/cpp/http.h b/android_app/app/src/main/cpp/http.h
index 94789ca..b940966 100644
--- a/android_app/app/src/main/cpp/http.h
+++ b/android_app/app/src/main/cpp/http.h
@@ -4,6 +4,10 @@
#include
+int get_header(const char *header, const char *data, size_t data_len, char *value);
+int next_header(const char **data, size_t *len);
+uint8_t *find_data(uint8_t *data, size_t data_len, char *value);
+
uint8_t *patch_http_url(uint8_t *data, size_t *data_len);
#endif //TUN2HTTP_TLS_H
diff --git a/android_app/app/src/main/cpp/tcp.c b/android_app/app/src/main/cpp/tcp.c
index c7b7837..4930fd5 100644
--- a/android_app/app/src/main/cpp/tcp.c
+++ b/android_app/app/src/main/cpp/tcp.c
@@ -265,7 +265,7 @@ void check_tcp_socket(const struct arguments *args,
if (s->tcp.state == TCP_LISTEN) {
// Check socket connect
if (ev->events & EPOLLIN) {
- uint8_t buffer[512];
+ char buffer[512];
ssize_t bytes = recv(s->socket, buffer, 12, 0);
if (bytes < 0) {
log_android(ANDROID_LOG_ERROR, "%s recv SOCKS5 error %d: %s",
@@ -274,14 +274,14 @@ void check_tcp_socket(const struct arguments *args,
} else {
if (s->tcp.connect_sent == TCP_CONNECT_SENT) {
buffer[bytes] = '\0';
- if (strcmp(buffer, "HTTP/1.0 200") == 0 || strcmp(buffer, "HTTP/1.1 200") == 0) {
s->tcp.connect_sent = TCP_CONNECT_ESTABLISHED;
while (recv(s->socket, buffer, sizeof(buffer), 0) > 0) {}
s->tcp.state = TCP_SYN_RECV;
} else {
write_rst(args, &s->tcp);
+ } if (strcmp(buffer, "HTTP/1.0 200") == 0 || strcmp(buffer, "HTTP/1.1 200") == 0) {
+
}
- }
}
} else {
s->tcp.remote_seq++; // remote SYN
@@ -504,8 +504,6 @@ jboolean handle_tcp(const struct arguments *args,
int uid,
const int epoll_fd) {
-
-
// Get headers
const uint8_t version = (*pkt) >> 4;
const struct iphdr *ip4 = (struct iphdr *) pkt;
@@ -591,7 +589,7 @@ jboolean handle_tcp(const struct arguments *args,
uint16_t mss = get_default_mss(version);
uint8_t ws = 0;
int optlen = tcpoptlen;
- uint8_t *options = tcpoptions;
+ const uint8_t * options = tcpoptions;
while (optlen > 0) {
uint8_t kind = *options;
uint8_t len = *(options + 1);
@@ -731,8 +729,8 @@ jboolean handle_tcp(const struct arguments *args,
}
if (cur->tcp.connect_sent == TCP_CONNECT_NOT_SENT) {
if (len > 0) {
- uint8_t buffer[512];
- sprintf(buffer, "CONNECT %s:443 HTTP/1.0\r\n\r\n", cur->tcp.hostname);
+ char buffer[512];
+ sprintf(buffer, "CONNECT %s:%d HTTP/1.0\r\n\r\n", cur->tcp.hostname, rport);
ssize_t sent = send(cur->socket, buffer, strlen(buffer), MSG_NOSIGNAL);
if (sent < 0) {
@@ -956,6 +954,7 @@ void queue_tcp(const struct arguments *args,
free(s->data);
s->data = malloc(datalen);
memcpy(s->data, data, datalen);
+ s->len = datalen;
} else
log_android(ANDROID_LOG_ERROR, "%s segment larger %u..%u < %u",
session,
diff --git a/android_app/app/src/main/cpp/tun2http.c b/android_app/app/src/main/cpp/tun2http.c
index 9c50d5e..4c8d701 100644
--- a/android_app/app/src/main/cpp/tun2http.c
+++ b/android_app/app/src/main/cpp/tun2http.c
@@ -48,7 +48,7 @@ void JNI_OnUnload(JavaVM *vm, void *reserved) {
// JNI ServiceSinkhole
JNIEXPORT void JNICALL
-Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject instance) {
+Java_tun_proxy_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject instance) {
loglevel = ANDROID_LOG_WARN;
struct arguments args;
@@ -72,7 +72,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1init(JNIEnv *env, jobject
}
JNIEXPORT void JNICALL
-Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1start(
+Java_tun_proxy_service_Tun2HttpVpnService_jni_1start(
JNIEnv *env, jobject instance, jint tun, jboolean fwd53, jint rcode, jstring proxyIp, jint proxyPort) {
const char *proxy_ip = (*env)->GetStringUTFChars(env, proxyIp, 0);
@@ -117,7 +117,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1start(
}
JNIEXPORT void JNICALL
-Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1stop(
+Java_tun_proxy_service_Tun2HttpVpnService_jni_1stop(
JNIEnv *env, jobject instance, jint tun) {
pthread_t t = thread_id;
log_android(ANDROID_LOG_WARN, "Stop tun %d thread %x", tun, t);
@@ -140,13 +140,13 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1stop(
}
JNIEXPORT jint JNICALL
-Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1get_1mtu(JNIEnv *env, jobject instance) {
+Java_tun_proxy_service_Tun2HttpVpnService_jni_1get_1mtu(JNIEnv *env, jobject instance) {
return get_mtu();
}
JNIEXPORT void JNICALL
-Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject instance) {
+Java_tun_proxy_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject instance) {
log_android(ANDROID_LOG_INFO, "Done");
clear();
@@ -162,7 +162,7 @@ Java_com_tun2http_app_service_Tun2HttpVpnService_jni_1done(JNIEnv *env, jobject
// JNI Util
JNIEXPORT jstring JNICALL
-Java_com_tun2http_app_utils_Util_jni_1getprop(JNIEnv *env, jclass type, jstring name_) {
+Java_tun_utils_Util_jni_1getprop(JNIEnv *env, jclass type, jstring name_) {
const char *name = (*env)->GetStringUTFChars(env, name_, 0);
char value[PROP_VALUE_MAX + 1] = "";
diff --git a/android_app/app/src/main/cpp/tun2http.h b/android_app/app/src/main/cpp/tun2http.h
index 129b12e..0fd3342 100644
--- a/android_app/app/src/main/cpp/tun2http.h
+++ b/android_app/app/src/main/cpp/tun2http.h
@@ -1,6 +1,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/android_app/app/src/main/java/com/tun2http/app/MainActivity.java b/android_app/app/src/main/java/tun/proxy/MainActivity.java
similarity index 51%
rename from android_app/app/src/main/java/com/tun2http/app/MainActivity.java
rename to android_app/app/src/main/java/tun/proxy/MainActivity.java
index f7baf39..f06ae0f 100644
--- a/android_app/app/src/main/java/com/tun2http/app/MainActivity.java
+++ b/android_app/app/src/main/java/tun/proxy/MainActivity.java
@@ -1,29 +1,45 @@
-package com.tun2http.app;
+package tun.proxy;
-import android.app.Activity;
+import android.net.VpnService;
+import android.os.Bundle;
+
+import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
-import android.net.VpnService;
+import android.content.pm.PackageManager;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceManager;
+
import android.os.Handler;
import android.os.IBinder;
-import android.preference.PreferenceManager;
-import android.os.Bundle;
+import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
+import android.view.Menu;
+import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;
-import com.tun2http.app.service.Tun2HttpVpnService;
+import tun.proxy.service.Tun2HttpVpnService;
+import tun.utils.IPUtil;
+
+public class MainActivity extends AppCompatActivity implements
+ PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+ public static final int REQUEST_VPN = 1;
+ public static final int REQUEST_CERT = 2;
-public class MainActivity extends Activity {
Button start;
Button stop;
EditText hostEditText;
-
- Handler statusHandler = new Handler();
+ Handler statusHandler = new Handler(Looper.getMainLooper());
private Tun2HttpVpnService service;
@@ -31,6 +47,8 @@ public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
start = findViewById(R.id.start);
stop = findViewById(R.id.stop);
@@ -48,14 +66,74 @@ public void onClick(View v) {
stopVpn();
}
});
-
-
start.setEnabled(true);
stop.setEnabled(false);
loadHostPort();
+
+ }
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
+ final Bundle args = pref.getExtras();
+ final Fragment fragment = getSupportFragmentManager().getFragmentFactory().instantiate(getClassLoader(), pref.getFragment());
+ fragment.setArguments(args);
+ fragment.setTargetFragment(caller, 0);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.activity_settings, fragment)
+ .addToBackStack(null)
+ .commit();
+ setTitle(pref.getTitle());
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem item = menu.findItem(R.id.action_activity_settings);
+ item.setEnabled(start.isEnabled());
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ switch (item.getItemId()) {
+ case R.id.action_activity_settings:
+ Intent intent = new android.content.Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ break;
+ case R.id.action_show_about:
+ new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.app_name) + getVersionName())
+ .setMessage(R.string.app_name)
+ .show();
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ return true;
}
+ protected String getVersionName() {
+ PackageManager packageManager = getPackageManager();
+ if (packageManager == null) {
+ return null;
+ }
+
+ try {
+ return packageManager.getPackageInfo(getPackageName(), 0).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
@@ -71,12 +149,11 @@ public void onServiceDisconnected(ComponentName className) {
@Override
protected void onResume() {
super.onResume();
-
start.setEnabled(false);
stop.setEnabled(false);
updateStatus();
- statusHandler.postDelayed(statusRunnable, 1000);
+ statusHandler.post(statusRunnable);
Intent intent = new Intent(this, Tun2HttpVpnService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
@@ -89,8 +166,8 @@ boolean isRunning() {
Runnable statusRunnable = new Runnable() {
@Override
public void run() {
- updateStatus();
- statusHandler.postDelayed(statusRunnable, 1000);
+ updateStatus();
+ statusHandler.post(statusRunnable);
}
};
@@ -98,7 +175,6 @@ public void run() {
protected void onPause() {
super.onPause();
statusHandler.removeCallbacks(statusRunnable);
-
unbindService(serviceConnection);
}
@@ -108,9 +184,11 @@ void updateStatus() {
}
if (isRunning()) {
start.setEnabled(false);
+ hostEditText.setEnabled(false);
stop.setEnabled(true);
} else {
start.setEnabled(true);
+ hostEditText.setEnabled(true);
stop.setEnabled(false);
}
}
@@ -118,25 +196,25 @@ void updateStatus() {
private void stopVpn() {
start.setEnabled(true);
stop.setEnabled(false);
-
Tun2HttpVpnService.stop(this);
}
private void startVpn() {
-
Intent i = VpnService.prepare(this);
if (i != null) {
- startActivityForResult(i, 0);
+ startActivityForResult(i, REQUEST_VPN);
} else {
- onActivityResult(0, Activity.RESULT_OK, null);
+ onActivityResult(REQUEST_VPN, RESULT_OK, null);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode == Activity.RESULT_OK && parseAndSaveHostPort()) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+ if (requestCode == REQUEST_VPN && parseAndSaveHostPort()) {
start.setEnabled(false);
stop.setEnabled(true);
Tun2HttpVpnService.start(this);
@@ -144,51 +222,39 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
private void loadHostPort() {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- String proxyHost = prefs.getString(Tun2HttpVpnService.PREF_PROXY_HOST, "");
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ final String proxyHost = prefs.getString(Tun2HttpVpnService.PREF_PROXY_HOST, "");
int proxyPort = prefs.getInt(Tun2HttpVpnService.PREF_PROXY_PORT, 0);
- if(TextUtils.isEmpty(proxyHost)) {
+ if (TextUtils.isEmpty(proxyHost)) {
return;
}
-
- if(proxyPort == 80) {
- hostEditText.setText(proxyHost);
- } else {
- hostEditText.setText(proxyHost + ":" + proxyPort);
- }
+ hostEditText.setText(proxyHost + ":" + proxyPort);
}
private boolean parseAndSaveHostPort() {
String hostPort = hostEditText.getText().toString();
- if (hostPort.isEmpty()) {
+ if (!IPUtil.isValidIPv4Address(hostPort)) {
hostEditText.setError(getString(R.string.enter_host));
return false;
}
-
String parts[] = hostPort.split(":");
int port = 0;
if (parts.length > 1) {
try {
port = Integer.parseInt(parts[1]);
- } catch (Exception e) {
+ } catch (NumberFormatException e) {
hostEditText.setError(getString(R.string.enter_host));
return false;
}
}
String[] ipParts = parts[0].split("\\.");
- if(ipParts.length != 4) {
- hostEditText.setError(getString(R.string.enter_host));
- return false;
- }
String host = parts[0];
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor edit = prefs.edit();
-
edit.putString(Tun2HttpVpnService.PREF_PROXY_HOST, host);
edit.putInt(Tun2HttpVpnService.PREF_PROXY_PORT, port);
-
edit.commit();
return true;
}
-}
+}
\ No newline at end of file
diff --git a/android_app/app/src/main/java/tun/proxy/MyApplication.java b/android_app/app/src/main/java/tun/proxy/MyApplication.java
new file mode 100644
index 0000000..1f67c79
--- /dev/null
+++ b/android_app/app/src/main/java/tun/proxy/MyApplication.java
@@ -0,0 +1,57 @@
+package tun.proxy;
+
+import android.app.Application;
+import android.content.SharedPreferences;
+import androidx.preference.PreferenceManager;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MyApplication extends Application {
+ private final static String PREF_VPN_MODE = "pref_vpn_connection_mode";
+ private final static String PREF_APP_KEY[] = {"pref_vpn_disallowed_application", "pref_vpn_allowed_application"};
+
+ private static MyApplication instance;
+
+ public static MyApplication getInstance() {
+ return instance;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ instance = this;
+ }
+
+ public enum VPNMode {DISALLOW, ALLOW};
+ public enum AppSortBy {APPNAME, PKGNAME};
+ public enum AppOrderBy {ASC, DESC};
+ public enum AppFiltertBy {APPNAME, PKGNAME};
+
+ public VPNMode loadVPNMode() {
+ final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ final String vpn_mode = sharedPreferences.getString(PREF_VPN_MODE, MyApplication.VPNMode.DISALLOW.name());
+ return VPNMode.valueOf(vpn_mode);
+ }
+
+ public void storeVPNMode(VPNMode mode) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(PREF_VPN_MODE, mode.name()).apply();
+ return;
+ }
+
+ public Set loadVPNApplication(VPNMode mode) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ final Set preference = prefs.getStringSet(PREF_APP_KEY[mode.ordinal()], new HashSet());
+ return preference;
+ }
+
+ public void storeVPNApplication(VPNMode mode, final Set set) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putStringSet(PREF_APP_KEY[mode.ordinal()], set).apply();
+ return;
+ }
+
+}
diff --git a/android_app/app/src/main/java/tun/proxy/SettingsActivity.java b/android_app/app/src/main/java/tun/proxy/SettingsActivity.java
new file mode 100644
index 0000000..d0011de
--- /dev/null
+++ b/android_app/app/src/main/java/tun/proxy/SettingsActivity.java
@@ -0,0 +1,684 @@
+package tun.proxy;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+//import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.*;
+
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.*;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import tun.utils.ProgressTask;
+
+public class SettingsActivity extends AppCompatActivity {
+ private static final String TAG = "SettingsActivity";
+ private static final String TITLE_TAG = "Settings";
+
+ public enum FilterAppType {
+ SYSTEM_APP,
+ OS_APP;
+
+ public static EnumSet parseEnumSet(String s) {
+ EnumSet filterType = EnumSet.noneOf(FilterAppType.class);
+ if (!s.startsWith("[") && s.endsWith("]")) {
+ throw new IllegalArgumentException("No enum constant " + FilterAppType.class.getCanonicalName() + "." + s);
+ }
+ String content = s.substring(1, s.length() - 1).trim();
+ if (content.isEmpty()) {
+ return filterType;
+ }
+ for (String t : content.split(",")) {
+ String v = t.trim();
+ filterType.add(Enum.valueOf(FilterAppType.class, v.replaceAll("\"", "")));
+ }
+ return filterType;
+ }
+
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.activity_settings, new SettingsFragment(), "preference_root")
+ .commit();
+ } else {
+ setTitle(savedInstanceState.getCharSequence(TITLE_TAG));
+ }
+ getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ setTitle(R.string.title_activity_settings);
+ }
+ }
+ });
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putCharSequence(TITLE_TAG, getTitle());
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ if (getSupportFragmentManager().popBackStackImmediate()) {
+ return true;
+ }
+ return super.onSupportNavigateUp();
+ }
+
+ /**
+ * Inner Classes.
+ */
+
+ public static class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener {
+ public static final String VPN_CONNECTION_MODE = "vpn_connection_mode";
+ public static final String VPN_DISALLOWED_APPLICATION_LIST = "vpn_disallowed_application_list";
+ public static final String VPN_ALLOWED_APPLICATION_LIST = "vpn_allowed_application_list";
+ public static final String VPN_CLEAR_ALL_SELECTION = "vpn_clear_all_selection";
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.preferences);
+ setHasOptionsMenu(true);
+
+ /* Allowed / Disallowed Application */
+ final ListPreference prefPackage = (ListPreference) this.findPreference(VPN_CONNECTION_MODE);
+ final PreferenceScreen prefDisallow = (PreferenceScreen) findPreference(VPN_DISALLOWED_APPLICATION_LIST);
+ final PreferenceScreen prefAllow = (PreferenceScreen) findPreference(VPN_ALLOWED_APPLICATION_LIST);
+ final PreferenceScreen clearAllSelection = (PreferenceScreen) findPreference(VPN_CLEAR_ALL_SELECTION);
+ clearAllSelection.setOnPreferenceClickListener(this);
+
+ prefPackage.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (preference instanceof ListPreference) {
+ final ListPreference listPreference = (ListPreference) preference;
+ int index = listPreference.findIndexOfValue((String) value);
+ prefDisallow.setEnabled(index == MyApplication.VPNMode.DISALLOW.ordinal());
+ prefAllow.setEnabled(index == MyApplication.VPNMode.ALLOW.ordinal());
+
+ // Set the summary to reflect the new value.
+ preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null);
+
+ MyApplication.VPNMode mode = MyApplication.VPNMode.values()[index];
+ MyApplication.getInstance().storeVPNMode(mode);
+ }
+ return true;
+ }
+ });
+ prefPackage.setSummary(prefPackage.getEntry());
+ prefDisallow.setEnabled(MyApplication.VPNMode.DISALLOW.name().equals(prefPackage.getValue()));
+ prefAllow.setEnabled(MyApplication.VPNMode.ALLOW.name().equals(prefPackage.getValue()));
+
+ updateMenuItem();
+ }
+
+ private void updateMenuItem() {
+ final PreferenceScreen prefDisallow = (PreferenceScreen) findPreference(VPN_DISALLOWED_APPLICATION_LIST);
+ final PreferenceScreen prefAllow = (PreferenceScreen) findPreference(VPN_ALLOWED_APPLICATION_LIST);
+
+ int countDisallow = MyApplication.getInstance().loadVPNApplication(MyApplication.VPNMode.DISALLOW).size();
+ int countAllow = MyApplication.getInstance().loadVPNApplication(MyApplication.VPNMode.ALLOW).size();
+ prefDisallow.setTitle(getString(R.string.pref_header_disallowed_application_list) + String.format(" (%d)", countDisallow));
+ prefAllow.setTitle(getString(R.string.pref_header_allowed_application_list) + String.format(" (%d)", countAllow));
+ }
+
+ /*
+ * https://developer.android.com/guide/topics/ui/settings/organize-your-settings
+ */
+
+ // リスナー部分
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ // keyを見てクリックされたPreferenceを特定
+ switch (preference.getKey()) {
+ case VPN_DISALLOWED_APPLICATION_LIST:
+ case VPN_ALLOWED_APPLICATION_LIST:
+ break;
+ case VPN_CLEAR_ALL_SELECTION:
+ new AlertDialog.Builder(getActivity())
+ .setTitle(getString(R.string.title_activity_settings))
+ .setMessage(getString(R.string.pref_dialog_clear_all_application_msg))
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Set set = new HashSet<>();
+ MyApplication.getInstance().storeVPNApplication(MyApplication.VPNMode.ALLOW, set);
+ MyApplication.getInstance().storeVPNApplication(MyApplication.VPNMode.DISALLOW, set);
+ updateMenuItem();
+ }
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+ break;
+ }
+ return false;
+ }
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class DisallowedPackageListFragment extends PackageListFragment {
+ public DisallowedPackageListFragment() {
+ super(MyApplication.VPNMode.DISALLOW);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class AllowedPackageListFragment extends PackageListFragment {
+ public AllowedPackageListFragment() {
+ super(MyApplication.VPNMode.ALLOW);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ protected static class PackageListFragment extends PreferenceFragmentCompat
+ implements SearchView.OnQueryTextListener, SearchView.OnCloseListener {
+ private final Map mAllPackageInfoMap = new HashMap<>();
+ private final static String PREF_VPN_APPLICATION_APP_TYPE = "pref_vpn_application_app_system";
+ private final static String PREF_VPN_APPLICATION_ORDER_BY = "pref_vpn_application_app_orderby";
+ private final static String PREF_VPN_APPLICATION_FILTER_BY = "pref_vpn_application_app_filterby";
+ private final static String PREF_VPN_APPLICATION_SORT_BY = "pref_vpn_application_app_sortby";
+
+ private AsyncTaskProgress task;
+
+ private MyApplication.VPNMode mode;
+
+ private EnumSet filterAppType = EnumSet.noneOf(FilterAppType.class);
+ private MyApplication.AppSortBy appSortBy = MyApplication.AppSortBy.APPNAME;
+ private MyApplication.AppOrderBy appOrderBy = MyApplication.AppOrderBy.ASC;
+ private MyApplication.AppSortBy appFilterBy = MyApplication.AppSortBy.APPNAME;
+ private PreferenceScreen mFilterPreferenceScreen;
+
+ public PackageListFragment(MyApplication.VPNMode mode) {
+ super();
+ this.mode = mode;
+ this.task = new AsyncTaskProgress(this);
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setHasOptionsMenu(true);
+ mFilterPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity());
+ setPreferenceScreen(mFilterPreferenceScreen);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ // Menuの設定
+ inflater.inflate(R.menu.menu_search, menu);
+ //MenuCompat.setGroupDividerEnabled(menu, true);
+
+ final MenuItem menuSearch = menu.findItem(R.id.menu_search_item);
+ this.searchView = (SearchView) menuSearch.getActionView();
+ this.searchView.setOnQueryTextListener(this);
+ this.searchView.setOnCloseListener(this);
+ this.searchView.setSubmitButtonEnabled(false);
+
+ final MenuItem menuShowSystemApp = menu.findItem(R.id.menu_filter_app_system);
+ menuShowSystemApp.setChecked(this.filterAppType.contains(FilterAppType.SYSTEM_APP));
+
+ switch (this.appOrderBy) {
+ case ASC: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_sort_order_asc);
+ menuItem.setChecked(true);
+ break;
+ }
+ case DESC: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_sort_order_desc);
+ menuItem.setChecked(true);
+ break;
+ }
+ }
+
+ switch (this.appFilterBy) {
+ case APPNAME: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_filter_app_name);
+ menuItem.setChecked(true);
+ break;
+ }
+ case PKGNAME: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_filter_pkg_name);
+ menuItem.setChecked(true);
+ break;
+ }
+ }
+
+ switch (this.appSortBy) {
+ case APPNAME: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_sort_app_name);
+ menuItem.setChecked(true);
+ break;
+ }
+ case PKGNAME: {
+ final MenuItem menuItem = menu.findItem(R.id.menu_sort_pkg_name);
+ menuItem.setChecked(true);
+ break;
+ }
+ }
+ }
+
+ private String searchFilter = "";
+ private SearchView searchView;
+
+ protected void filter(String filter) {
+ this.filter(filter, this.appFilterBy, this.appOrderBy, this.appSortBy, this.filterAppType);
+ }
+
+ protected void filter(String filter, final MyApplication.AppSortBy filterBy, final MyApplication.AppOrderBy orderBy, final MyApplication.AppSortBy sortBy, EnumSet filterAppType) {
+ if (filter == null) {
+ filter = searchFilter;
+ } else {
+ searchFilter = filter;
+ }
+ this.filterAppType = filterAppType;
+ this.appFilterBy = filterBy;
+ this.appOrderBy = orderBy;
+ this.appSortBy = sortBy;
+
+ Set selected = this.getAllSelectedPackageSet();
+ storeSelectedPackageSet(selected);
+
+ this.removeAllPreferenceScreen();
+
+ if (task != null && task.getStatus() == ProgressTask.Status.PENDING) {
+ task.execute();
+ }
+ else {
+ task = new AsyncTaskProgress(this);
+ task.execute();
+ }
+// this.filterPackagesPreferences(filter, sortBy, orderBy);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (this.task != null) {
+ this.task.cancel(true);
+ this.task = null;
+ }
+
+ Set selected = this.getAllSelectedPackageSet();
+ storeSelectedPackageSet(selected);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MyApplication.getInstance().getApplicationContext());
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString(PREF_VPN_APPLICATION_APP_TYPE, this.filterAppType.toString());
+ edit.putString(PREF_VPN_APPLICATION_ORDER_BY, this.appOrderBy.name());
+ edit.putString(PREF_VPN_APPLICATION_FILTER_BY, this.appFilterBy.name());
+ edit.putString(PREF_VPN_APPLICATION_SORT_BY, this.appSortBy.name());
+ edit.apply();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Set loadMap = MyApplication.getInstance().loadVPNApplication(this.mode);
+ for (String pkgName : loadMap) {
+ this.mAllPackageInfoMap.put(pkgName, loadMap.contains(pkgName));
+ }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MyApplication.getInstance().getApplicationContext());
+ String filterAppType = prefs.getString(PREF_VPN_APPLICATION_APP_TYPE, this.filterAppType.toString());
+ this.filterAppType = FilterAppType.parseEnumSet(filterAppType);
+ String appOrderBy = prefs.getString(PREF_VPN_APPLICATION_ORDER_BY, MyApplication.AppOrderBy.ASC.name());
+ String appFilterBy = prefs.getString(PREF_VPN_APPLICATION_FILTER_BY, MyApplication.AppSortBy.APPNAME.name());
+ String appSortBy = prefs.getString(PREF_VPN_APPLICATION_SORT_BY, MyApplication.AppSortBy.APPNAME.name());
+ this.appOrderBy = Enum.valueOf(MyApplication.AppOrderBy.class, appOrderBy);
+ this.appFilterBy = Enum.valueOf(MyApplication.AppSortBy.class, appFilterBy);
+ this.appSortBy = Enum.valueOf(MyApplication.AppSortBy.class, appSortBy);
+ filter(null);
+ }
+
+ private void removeAllPreferenceScreen() {
+ this.mFilterPreferenceScreen.removeAll();
+ }
+
+// private void filterPackagesPreferences(String filter, final MyApplication.AppSortBy sortBy, final MyApplication.AppOrderBy orderBy) {
+// final Context context = MyApplication.getInstance().getApplicationContext();
+// final PackageManager pm = context.getPackageManager();
+// final List installedPackages = pm.getInstalledPackages(PackageManager.GET_META_DATA);
+// Collections.sort(installedPackages, new Comparator() {
+// @Override
+// public int compare(PackageInfo o1, PackageInfo o2) {
+// String t1 = "";
+// String t2 = "";
+// switch (sortBy) {
+// case APPNAME:
+// t1 = o1.applicationInfo.loadLabel(pm).toString();
+// t2 = o2.applicationInfo.loadLabel(pm).toString();
+// break;
+// case PKGNAME:
+// t1 = o1.packageName;
+// t2 = o2.packageName;
+// break;
+// }
+// if (MyApplication.AppOrderBy.ASC.equals(orderBy))
+// return t1.compareTo(t2);
+// else
+// return t2.compareTo(t1);
+// }
+// });
+//
+// final Map installedPackageMap = new HashMap<>();
+// for (final PackageInfo pi : installedPackages) {
+// // exclude self package
+// if (pi.packageName.equals(MyApplication.getInstance().getPackageName())) {
+// continue;
+// }
+// boolean checked = this.mAllPackageInfoMap.containsKey(pi.packageName) ? this.mAllPackageInfoMap.get(pi.packageName) : false;
+// installedPackageMap.put(pi.packageName, checked);
+// }
+// this.mAllPackageInfoMap.clear();
+// this.mAllPackageInfoMap.putAll(installedPackageMap);
+//
+// for (final PackageInfo pi : installedPackages) {
+// // exclude self package
+// if (pi.packageName.equals(MyApplication.getInstance().getPackageName())) {
+// continue;
+// }
+// String t1 = pi.applicationInfo.loadLabel(pm).toString();
+// if (filter.trim().isEmpty() || t1.toLowerCase().contains(filter.toLowerCase())) {
+// final Preference preference = buildPackagePreferences(pm, pi);
+// this.mFilterPreferenceScreen.addPreference(preference);
+// }
+// }
+// }
+
+ private Preference buildPackagePreferences(final PackageManager pm, final PackageInfo pi) {
+ final CheckBoxPreference prefCheck = new CheckBoxPreference(getActivity());
+ prefCheck.setIcon(pi.applicationInfo.loadIcon(pm));
+ prefCheck.setTitle(pi.applicationInfo.loadLabel(pm).toString());
+ prefCheck.setSummary(pi.packageName);
+ boolean checked = this.mAllPackageInfoMap.containsKey(pi.packageName) ? this.mAllPackageInfoMap.get(pi.packageName) : false;
+ prefCheck.setChecked(checked);
+ Preference.OnPreferenceClickListener click = new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ mAllPackageInfoMap.put(prefCheck.getSummary().toString(), prefCheck.isChecked());
+ return false;
+ }
+ };
+ prefCheck.setOnPreferenceClickListener(click);
+ return prefCheck;
+ }
+
+ private Set getFilterSelectedPackageSet() {
+ final Set selected = new HashSet<>();
+ for (int i = 0; i < this.mFilterPreferenceScreen.getPreferenceCount(); i++) {
+ Preference pref = this.mFilterPreferenceScreen.getPreference(i);
+ if ((pref instanceof CheckBoxPreference)) {
+ CheckBoxPreference prefCheck = (CheckBoxPreference) pref;
+ if (prefCheck.isChecked()) {
+ selected.add(prefCheck.getSummary().toString());
+ }
+ }
+ }
+ return selected;
+ }
+
+ private void setSelectedPackageSet(Set selected) {
+ for (int i = 0; i < this.mFilterPreferenceScreen.getPreferenceCount(); i++) {
+ Preference pref = this.mFilterPreferenceScreen.getPreference(i);
+ if ((pref instanceof CheckBoxPreference)) {
+ CheckBoxPreference prefCheck = (CheckBoxPreference) pref;
+ if (selected.contains((prefCheck.getSummary()))) {
+ prefCheck.setChecked(true);
+ }
+ }
+ }
+ }
+
+ private void clearAllSelectedPackageSet() {
+ final Set selected = this.getFilterSelectedPackageSet();
+ for (Map.Entry value : this.mAllPackageInfoMap
+ .entrySet()) {
+ if (value.getValue()) {
+ value.setValue(false);
+ }
+ }
+ }
+
+ private Set getAllSelectedPackageSet() {
+ final Set selected = this.getFilterSelectedPackageSet();
+ for (Map.Entry value : this.mAllPackageInfoMap.entrySet()) {
+ if (value.getValue()) {
+ selected.add(value.getKey());
+ }
+ }
+ return selected;
+ }
+
+ private void storeSelectedPackageSet(final Set set) {
+ MyApplication.getInstance().storeVPNMode(this.mode);
+ MyApplication.getInstance().storeVPNApplication(this.mode, set);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case android.R.id.home:
+ startActivity(new Intent(getActivity(), SettingsActivity.class));
+ return true;
+ case R.id.menu_filter_app_system:
+ item.setChecked(!item.isChecked());
+ if (item.isChecked()) {
+ this.filterAppType.add(FilterAppType.SYSTEM_APP);
+ }
+ else {
+ this.filterAppType.remove(FilterAppType.SYSTEM_APP);
+ }
+ filter(null, appFilterBy, MyApplication.AppOrderBy.ASC, appSortBy, this.filterAppType);
+ break;
+ case R.id.menu_sort_order_asc:
+ item.setChecked(!item.isChecked());
+ filter(null, appFilterBy, MyApplication.AppOrderBy.ASC, appSortBy, this.filterAppType);
+ break;
+ case R.id.menu_sort_order_desc:
+ item.setChecked(!item.isChecked());
+ filter(null, appFilterBy, MyApplication.AppOrderBy.DESC, appSortBy, this.filterAppType);
+ break;
+ case R.id.menu_filter_app_name:
+ item.setChecked(!item.isChecked());
+ this.appFilterBy = MyApplication.AppSortBy.APPNAME;
+ //filter(null, MyApplication.AppSortBy.APPNAME, appOrderBy, appSortBy);
+ break;
+ case R.id.menu_filter_pkg_name:
+ item.setChecked(!item.isChecked());
+ this.appFilterBy = MyApplication.AppSortBy.PKGNAME;
+ //filter(null, MyApplication.AppSortBy.PKGNAME, appOrderBy, appSortBy);
+ break;
+ case R.id.menu_sort_app_name:
+ item.setChecked(!item.isChecked());
+ filter(null, appFilterBy, appOrderBy, MyApplication.AppSortBy.APPNAME, this.filterAppType);
+ break;
+ case R.id.menu_sort_pkg_name:
+ item.setChecked(!item.isChecked());
+ filter(null, appFilterBy, appOrderBy, MyApplication.AppSortBy.PKGNAME, this.filterAppType);
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ this.searchView.clearFocus();
+ if (!query.trim().isEmpty()) {
+ filter(query);
+ return true;
+ } else {
+ filter("");
+ return true;
+ }
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ return false;
+ }
+
+ @Override
+ public boolean onClose() {
+ Set selected = this.getAllSelectedPackageSet();
+ storeSelectedPackageSet(selected);
+ filter("");
+ return false;
+ }
+ }
+
+ /*
+ * AsyncTask
+ * https://developer.android.com/reference/android/os/AsyncTask
+ * Deprecated in API level R
+ * */
+ public static class AsyncTaskProgress extends ProgressTask> {
+
+ final PackageListFragment packageFragment;
+
+ public AsyncTaskProgress(PackageListFragment packageFragment) {
+ this.packageFragment = packageFragment;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ packageFragment.mFilterPreferenceScreen.addPreference(new ProgressPreference(packageFragment.getActivity()));
+ }
+
+ @Override
+ protected List doInBackground(String... params) {
+ return filterPackages(packageFragment.searchFilter, packageFragment.appFilterBy, packageFragment.appOrderBy, packageFragment.appSortBy, packageFragment.filterAppType);
+ }
+
+ private List filterPackages(String filter, final MyApplication.AppSortBy filterBy, final MyApplication.AppOrderBy orderBy, final MyApplication.AppSortBy sortBy, EnumSet filterAppType) {
+ final Context context = MyApplication.getInstance().getApplicationContext();
+ final PackageManager pm = context.getPackageManager();
+ final List installedPackages = pm.getInstalledPackages(PackageManager.GET_META_DATA);
+ Collections.sort(installedPackages, new Comparator() {
+ @Override
+ public int compare(PackageInfo o1, PackageInfo o2) {
+ String t1 = "";
+ String t2 = "";
+ switch (sortBy) {
+ case APPNAME:
+ t1 = o1.applicationInfo.loadLabel(pm).toString();
+ t2 = o2.applicationInfo.loadLabel(pm).toString();
+ break;
+ case PKGNAME:
+ t1 = o1.packageName;
+ t2 = o2.packageName;
+ break;
+ }
+ if (MyApplication.AppOrderBy.ASC.equals(orderBy))
+ return t1.compareTo(t2);
+ else
+ return t2.compareTo(t1);
+ }
+ });
+ final Map installedPackageMap = new HashMap<>();
+ for (final PackageInfo pi : installedPackages) {
+ if (isCancelled()) continue;
+ // exclude self package
+ if (pi.packageName.equals(MyApplication.getInstance().getPackageName())) {
+ continue;
+ }
+ // exclude system app
+ if (!filterAppType.contains(FilterAppType.SYSTEM_APP)) {
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM) {
+ continue;
+ }
+ }
+ boolean checked = packageFragment.mAllPackageInfoMap.containsKey(pi.packageName) ? packageFragment.mAllPackageInfoMap.get(pi.packageName) : false;
+ installedPackageMap.put(pi.packageName, checked);
+ }
+ packageFragment.mAllPackageInfoMap.clear();
+ packageFragment.mAllPackageInfoMap.putAll(installedPackageMap);
+ return installedPackages;
+ }
+
+ @Override
+ protected void onPostExecute(List installedPackages) {
+ final Context context = MyApplication.getInstance().getApplicationContext();
+ final PackageManager pm = context.getPackageManager();
+ packageFragment.mFilterPreferenceScreen.removeAll();
+ for (final PackageInfo pi : installedPackages) {
+ // exclude self package
+ if (pi.packageName.equals(MyApplication.getInstance().getPackageName())) {
+ continue;
+ }
+ // exclude system app
+ if (!packageFragment.filterAppType.contains(FilterAppType.SYSTEM_APP)) {
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM) {
+ continue;
+ }
+ }
+ String t1 = "";
+ String t2 = packageFragment.searchFilter.trim();
+ switch (packageFragment.appFilterBy) {
+ case APPNAME:
+ t1 = pi.applicationInfo.loadLabel(pm).toString();
+ break;
+ case PKGNAME:
+ t1 = pi.packageName;
+ break;
+ }
+ if (t2.isEmpty() || t1.toLowerCase().contains(t2.toLowerCase())) {
+ final Preference preference = packageFragment.buildPackagePreferences(pm, pi);
+ packageFragment.mFilterPreferenceScreen.addPreference(preference);
+ }
+ }
+ return;
+ }
+
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ packageFragment.mAllPackageInfoMap.clear();
+ packageFragment.mFilterPreferenceScreen.removeAll();
+ return;
+ }
+
+ }
+
+ protected static class ProgressPreference extends Preference {
+ public ProgressPreference(Context context){
+ super(context);
+ setLayoutResource(R.layout.preference_progress);
+ }
+ }
+}
+
diff --git a/android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java b/android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java
similarity index 52%
rename from android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java
rename to android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java
index 57aef69..b6c2c81 100644
--- a/android_app/app/src/main/java/com/tun2http/app/receiver/BootReceiver.java
+++ b/android_app/app/src/main/java/tun/proxy/receiver/BootReceiver.java
@@ -1,33 +1,33 @@
-package com.tun2http.app.receiver;
+package tun.proxy.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.VpnService;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
+import tun.proxy.R;
-import com.tun2http.app.service.Tun2HttpVpnService;
-
+import tun.proxy.service.Tun2HttpVpnService;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
- if(intent != null && !Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ if (intent != null && !Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
return;
}
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean isRunning = prefs.getBoolean(Tun2HttpVpnService.PREF_RUNNING, false);
- if(isRunning) {
+ if (isRunning) {
Intent prepare = VpnService.prepare(context);
- if(prepare == null) {
- Log.d("Tun2Http.Boot", "Starting vpn");
+ if (prepare == null) {
+ Log.d(context.getString(R.string.app_name) + ".Boot", "Starting vpn");
Tun2HttpVpnService.start(context);
} else {
- Log.d("Tun2Http.Boot", "Not prepared");
+ Log.d(context.getString(R.string.app_name) + ".Boot", "Not prepared");
}
}
}
diff --git a/android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java b/android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java
similarity index 68%
rename from android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java
rename to android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java
index ed888ff..107e20e 100644
--- a/android_app/app/src/main/java/com/tun2http/app/service/Tun2HttpVpnService.java
+++ b/android_app/app/src/main/java/tun/proxy/service/Tun2HttpVpnService.java
@@ -1,12 +1,9 @@
-package com.tun2http.app.service;
+package tun.proxy.service;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.net.VpnService;
import android.os.Binder;
import android.os.Build;
@@ -15,46 +12,57 @@
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
-import com.tun2http.app.R;
-import com.tun2http.app.utils.IPUtil;
-import com.tun2http.app.utils.Util;
-
import java.io.IOException;
-import java.math.BigInteger;
-import java.net.Inet4Address;
import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
+import tun.proxy.MyApplication;
+import tun.proxy.R;
+import tun.utils.Util;
public class Tun2HttpVpnService extends VpnService {
- private static final String TAG = "Tun2Http.Service";
- private static final String ACTION_START = "start";
- private static final String ACTION_STOP = "stop";
public static final String PREF_PROXY_HOST = "pref_proxy_host";
public static final String PREF_PROXY_PORT = "pref_proxy_port";
public static final String PREF_RUNNING = "pref_running";
+ private static final String TAG = "Tun2Http.Service";
+ private static final String ACTION_START = "start";
+ private static final String ACTION_STOP = "stop";
+ private static volatile PowerManager.WakeLock wlInstance = null;
+ static {
+ System.loadLibrary("tun2http");
+ }
private Tun2HttpVpnService.Builder lastBuilder = null;
private ParcelFileDescriptor vpn = null;
- static {
- System.loadLibrary("tun2http");
+ synchronized private static PowerManager.WakeLock getLock(Context context) {
+ if (wlInstance == null) {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ wlInstance = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getString(R.string.app_name) + " wakelock");
+ wlInstance.setReferenceCounted(true);
+ }
+ return wlInstance;
}
+ public static void start(Context context) {
+ Intent intent = new Intent(context, Tun2HttpVpnService.class);
+ intent.setAction(ACTION_START);
+ context.startService(intent);
+ }
- private static volatile PowerManager.WakeLock wlInstance = null;
+ public static void stop(Context context) {
+ Intent intent = new Intent(context, Tun2HttpVpnService.class);
+ intent.setAction(ACTION_STOP);
+ context.startService(intent);
+ }
private native void jni_init();
@@ -64,40 +72,13 @@ public class Tun2HttpVpnService extends VpnService {
private native int jni_get_mtu();
- private native int jni_done();
-
-
- synchronized private static PowerManager.WakeLock getLock(Context context) {
- if (wlInstance == null) {
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- wlInstance = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getString(R.string.app_name) + " wakelock");
- wlInstance.setReferenceCounted(true);
- }
- return wlInstance;
- }
+ private native void jni_done();
@Override
public IBinder onBind(Intent intent) {
return new ServiceBinder();
}
- public class ServiceBinder extends Binder {
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- // see Implementation of android.net.VpnService.Callback.onTransact()
- if (code == IBinder.LAST_CALL_TRANSACTION) {
- onRevoke();
- return true;
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- public Tun2HttpVpnService getService() {
- return Tun2HttpVpnService.this;
- }
- }
-
public boolean isRunning() {
return vpn != null;
}
@@ -122,7 +103,6 @@ private void stop() {
stopForeground(true);
}
-
@Override
public void onRevoke() {
Log.i(TAG, "Revoke");
@@ -158,20 +138,36 @@ private Builder getBuilder() {
builder.addAddress(vpn6, 128);
builder.addRoute("0.0.0.0", 0);
-
builder.addRoute("0:0:0:0:0:0:0:0", 0);
+ List dnsList = Util.getDefaultDNS(MyApplication.getInstance().getApplicationContext());
+ for (String dns : dnsList) {
+ Log.i(TAG, "default DNS:" + dns);
+ builder.addDnsServer(dns);
+ }
+
// MTU
int mtu = jni_get_mtu();
Log.i(TAG, "MTU=" + mtu);
builder.setMtu(mtu);
- // Add list of allowed applications
+ // AAdd list of allowed and disallowed applications
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- try {
- builder.addAllowedApplication("com.android.chrome");
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ MyApplication app = (MyApplication) this.getApplication();
+ if (app.loadVPNMode() == MyApplication.VPNMode.DISALLOW) {
+ Set disallow = app.loadVPNApplication(MyApplication.VPNMode.DISALLOW);
+ Log.d(TAG, "disallowed:" + disallow.size());
+ List notFoundPackageList = new ArrayList<>();
+ builder.addDisallowedApplication(Arrays.asList(disallow.toArray(new String[0])), notFoundPackageList);
+ disallow.removeAll(notFoundPackageList);
+ MyApplication.getInstance().storeVPNApplication(MyApplication.VPNMode.DISALLOW, disallow);
+ } else {
+ Set allow = app.loadVPNApplication(MyApplication.VPNMode.ALLOW);
+ Log.d(TAG, "allowed:" + allow.size());
+ List notFoundPackageList = new ArrayList<>();
+ builder.addAllowedApplication(Arrays.asList(allow.toArray(new String[0])), notFoundPackageList);
+ allow.removeAll(notFoundPackageList);
+ MyApplication.getInstance().storeVPNApplication(MyApplication.VPNMode.ALLOW, allow);
}
}
@@ -179,7 +175,6 @@ private Builder getBuilder() {
return builder;
}
-
private void startNative(ParcelFileDescriptor vpn) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String proxyHost = prefs.getString(PREF_PROXY_HOST, "");
@@ -205,7 +200,6 @@ private void stopNative(ParcelFileDescriptor vpn) {
prefs.edit().putBoolean(PREF_RUNNING, false).apply();
}
-
private void stopVPN(ParcelFileDescriptor pfd) {
Log.i(TAG, "Stopping");
try {
@@ -229,7 +223,6 @@ private void nativeError(int error, String message) {
Log.w(TAG, "Native error " + error + ": " + message);
}
-
private boolean isSupported(int protocol) {
return (protocol == 1 /* ICMPv4 */ ||
protocol == 59 /* ICMPv6 */ ||
@@ -237,13 +230,10 @@ private boolean isSupported(int protocol) {
protocol == 17 /* UDP */);
}
-
@Override
public void onCreate() {
-
// Native init
jni_init();
-
super.onCreate();
}
@@ -265,7 +255,6 @@ public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
-
@Override
public void onDestroy() {
Log.i(TAG, "Destroy");
@@ -285,17 +274,31 @@ public void onDestroy() {
super.onDestroy();
}
+ public class ServiceBinder extends Binder {
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ // see Implementation of android.net.VpnService.Callback.onTransact()
+ if (code == IBinder.LAST_CALL_TRANSACTION) {
+ onRevoke();
+ return true;
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ public Tun2HttpVpnService getService() {
+ return Tun2HttpVpnService.this;
+ }
+ }
+
private class Builder extends VpnService.Builder {
- private NetworkInfo networkInfo;
private int mtu;
private List listAddress = new ArrayList<>();
private List listRoute = new ArrayList<>();
- private List listDns = new ArrayList<>();
+ private List listDns = new ArrayList<>();
private Builder() {
super();
- ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- networkInfo = cm.getActiveNetworkInfo();
}
@Override
@@ -320,12 +323,54 @@ public Builder addRoute(String address, int prefixLength) {
}
@Override
- public Builder addDnsServer(InetAddress address) {
- listDns.add(address);
+ public Builder addDnsServer(InetAddress address) {
+ listDns.add(address.getHostAddress());
+ super.addDnsServer(address);
+ return this;
+ }
+
+ @Override
+ public Builder addDnsServer(String address) {
+// listDns.add(address);
super.addDnsServer(address);
return this;
}
+ // min sdk 26
+ public Builder addAllowedApplication(final List packageList, final List notFoundPackegeList) {
+ for (String pkg : packageList) {
+ try {
+ Log.i(TAG, "allowed:" + pkg);
+ addAllowedApplication(pkg);
+ } catch (PackageManager.NameNotFoundException e) {
+ notFoundPackegeList.add(pkg);
+ }
+ }
+ return this;
+ }
+
+ public Builder addDisallowedApplication(final List packageList) throws PackageManager.NameNotFoundException {
+ //
+ for (String pkg : packageList) {
+ Log.i(TAG, "disallowed:" + pkg);
+ addDisallowedApplication(pkg);
+ }
+ return this;
+ }
+
+ public Builder addDisallowedApplication(final List packageList, final List notFoundPackegeList) {
+ //
+ for (String pkg : packageList) {
+ try {
+ Log.i(TAG, "disallowed:" + pkg);
+ addDisallowedApplication(pkg);
+ } catch (PackageManager.NameNotFoundException e) {
+ notFoundPackegeList.add(pkg);
+ }
+ }
+ return this;
+ }
+
@Override
public boolean equals(Object obj) {
Builder other = (Builder) obj;
@@ -333,10 +378,6 @@ public boolean equals(Object obj) {
if (other == null)
return false;
- if (this.networkInfo == null || other.networkInfo == null ||
- this.networkInfo.getType() != other.networkInfo.getType())
- return false;
-
if (this.mtu != other.mtu)
return false;
@@ -357,25 +398,31 @@ public boolean equals(Object obj) {
if (!other.listRoute.contains(route))
return false;
- for (InetAddress dns : this.listDns)
+ for (String dns : this.listDns)
if (!other.listDns.contains(dns))
return false;
return true;
}
- }
-
- public static void start(Context context) {
- Intent intent = new Intent(context, Tun2HttpVpnService.class);
- intent.setAction(ACTION_START);
- context.startService(intent);
+// public boolean isNetworkConnected() {
+// final ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+// if (cm != null) {
+// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+// final android.net.NetworkInfo ni = cm.getActiveNetworkInfo();
+// if (ni != null) {
+// return (ni.isConnected() && (ni.getType() == ConnectivityManager.TYPE_WIFI || ni.getType() == ConnectivityManager.TYPE_MOBILE));
+// }
+// } else {
+// final Network n = cm.getActiveNetwork();
+// if (n != null) {
+// final NetworkCapabilities nc = cm.getNetworkCapabilities(n);
+// return (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+// }
+// }
+// }
+// return false;
+// }
}
-
- public static void stop(Context context) {
- Intent intent = new Intent(context, Tun2HttpVpnService.class);
- intent.setAction(ACTION_STOP);
- context.startService(intent);
- }
}
diff --git a/android_app/app/src/main/java/tun/utils/CertificateUtil.java b/android_app/app/src/main/java/tun/utils/CertificateUtil.java
new file mode 100644
index 0000000..4f6b068
--- /dev/null
+++ b/android_app/app/src/main/java/tun/utils/CertificateUtil.java
@@ -0,0 +1,207 @@
+package tun.utils;
+
+import android.content.Intent;
+import android.security.KeyChain;
+import android.util.Log;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CertificateUtil {
+ private static final String TAG = "CertificateManager";
+
+ public enum CertificateInstallType {SYSTEM, USER};
+
+ private final static Pattern CA_COMMON_NAME = Pattern.compile("CN=([^,]+),?.*$");
+ private final static Pattern CA_ORGANIZATION = Pattern.compile("O=([^,]+),?.*$");
+
+ public static boolean findCAStore(String caName) {
+ boolean found = false;
+ try {
+ KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+ if (ks == null)
+ return false;
+
+ ks.load(null, null);
+ X509Certificate rootCACert = null;
+ Enumeration aliases = ks.aliases();
+ while (aliases.hasMoreElements()) {
+ String alias = (String) aliases.nextElement();
+ rootCACert = (X509Certificate) ks.getCertificate(alias);
+ if (rootCACert.getIssuerDN().getName().contains(caName)) {
+ found = true;
+ break;
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (CertificateException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return found;
+ }
+
+ public static List getRootCAStore() {
+ final List rootCAList = new ArrayList<>();
+ try {
+ KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+ if (ks == null)
+ return null;
+
+ ks.load(null, null);
+ X509Certificate rootCACert = null;
+ Enumeration aliases = ks.aliases();
+ boolean found = false;
+ while (aliases.hasMoreElements()) {
+ String alias = (String) aliases.nextElement();
+ X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
+ System.out.println(alias + "/" + cert.getIssuerX500Principal().getName());
+ rootCAList.add(cert);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (CertificateException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return rootCAList;
+ }
+
+ public static Map getRootCAMap(EnumSet type) {
+ final Map rootCAMap = new HashMap<>();
+ try {
+ KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+ if (ks == null) {
+ return null;
+ }
+
+ ks.load(null, null);
+ X509Certificate rootCACert = null;
+ Enumeration aliases = ks.aliases();
+ List certList = new ArrayList<>();
+ while (aliases.hasMoreElements()) {
+ String alias = (String) aliases.nextElement();
+ X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
+ if (type.contains(CertificateInstallType.SYSTEM) && alias.startsWith("system:")) {
+ certList.add(cert);
+ }
+ if (type.contains(CertificateInstallType.USER) && alias.startsWith("user:")) {
+ certList.add(cert);
+ }
+ }
+ Collections.sort(certList, new Comparator() {
+ @Override
+ public int compare(X509Certificate t1, X509Certificate t2) {
+ String t1cn = CertificateUtil.getCommonName(t1.getIssuerX500Principal().getName());
+ String t2cn = CertificateUtil.getCommonName(t2.getIssuerX500Principal().getName());
+ return t1cn.compareToIgnoreCase(t2cn);
+ }
+ });
+ // ソート後
+ List rootCANameList = new ArrayList<>();
+ List rootCAList = new ArrayList<>();
+ for (X509Certificate cert : certList) {
+ String cn = CertificateUtil.getCommonName(cert.getIssuerX500Principal().getName());
+ if (cn.trim().isEmpty()) continue;
+ //String o = CertificateUtil.getOrganization( cert.getIssuerX500Principal().getName());
+ rootCANameList.add(cn);
+ rootCAList.add(encode(cert.getEncoded()));
+ }
+ rootCAMap.put("entry", rootCANameList.toArray(new String[0]));
+ rootCAMap.put("value", rootCAList.toArray(new String[0]));
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (KeyStoreException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (CertificateException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return rootCAMap;
+ }
+
+ public static String encode(byte b[]) {
+ return new String(b, StandardCharsets.ISO_8859_1);
+ }
+
+ public static byte[] decode(String s) {
+ return s.getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ public static Intent trustRootCA(X509Certificate cert) {
+ Log.d(TAG, "root CA is not yet trusted");
+ Intent intent = KeyChain.createInstallIntent();
+ try {
+ if (findCAStore(cert.getIssuerDN().getName())) return null;
+ intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert.getEncoded());
+ intent.putExtra(KeyChain.EXTRA_NAME, getCommonName(cert.getIssuerDN().getName()));
+ } catch (CertificateEncodingException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return intent;
+ }
+
+ // get the CA certificate by the path
+ public static X509Certificate getCACertificate(byte[] buff) {
+ X509Certificate ca = null;
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ ca = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(buff));
+ } catch (CertificateException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return ca;
+ }
+
+ // get the CA certificate by the path
+ public static X509Certificate getCACertificate(File caFile) {
+ try (InputStream inStream = new FileInputStream(caFile)) {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(inStream);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (CertificateException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return null;
+ }
+
+ public static String getCommonName(String dn) {
+ String cn = "";
+ Matcher m = CA_COMMON_NAME.matcher(dn);
+ if (m.find()) {
+ cn = m.group(1);
+ }
+ return cn;
+ }
+
+ public static String getOrganization(String dn) {
+ String on = "";
+ Matcher m = CA_ORGANIZATION.matcher(dn);
+ if (m.find()) {
+ on = m.group(1);
+ }
+ return on;
+ }
+
+}
diff --git a/android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java b/android_app/app/src/main/java/tun/utils/IPUtil.java
similarity index 77%
rename from android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java
rename to android_app/app/src/main/java/tun/utils/IPUtil.java
index 7ddf61f..cdc4d4a 100644
--- a/android_app/app/src/main/java/com/tun2http/app/utils/IPUtil.java
+++ b/android_app/app/src/main/java/tun/utils/IPUtil.java
@@ -1,5 +1,4 @@
-package com.tun2http.app.utils;
-
+package tun.utils;
import android.util.Log;
@@ -11,6 +10,42 @@
public class IPUtil {
private static final String TAG = "Tun2Http.IPUtil";
+ public static boolean isValidIPv4Address(String address) {
+ if (address.isEmpty()) {
+ return false;
+ }
+ String parts[] = address.split(":");
+ int port = 0;
+ if (parts.length > 1) {
+ try {
+ port = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ if (!(0 < port && port < 65536)) {
+ return false;
+ }
+ }
+ String[] ipParts = parts[0].split("\\.");
+ if (ipParts.length != 4) {
+ return false;
+ }
+ else {
+ for (int i = 0; i < ipParts.length; i++) {
+ int ipPart = -1;
+ try {
+ ipPart = Integer.parseInt(ipParts[i]);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ if (!(0 <= ipPart && ipPart <= 255)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
public static List toCIDR(String start, String end) throws UnknownHostException {
return toCIDR(InetAddress.getByName(start), InetAddress.getByName(end));
}
diff --git a/android_app/app/src/main/java/tun/utils/ProgressTask.java b/android_app/app/src/main/java/tun/utils/ProgressTask.java
new file mode 100644
index 0000000..abd79f3
--- /dev/null
+++ b/android_app/app/src/main/java/tun/utils/ProgressTask.java
@@ -0,0 +1,88 @@
+package tun.utils;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public abstract class ProgressTask {
+ private volatile Status mStatus = Status.PENDING;
+ private boolean canceled = false;
+
+ public final ProgressTask.Status getStatus() {
+ return mStatus;
+ }
+
+ private class ProgressRunnable implements Runnable {
+
+ final Params [] params;
+
+ @SafeVarargs
+ public ProgressRunnable(Params... params) {
+ this.params = params;
+ }
+
+ private Result result;
+ Handler handler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void run() {
+ if (mStatus != Status.PENDING) {
+ switch (mStatus) {
+ case RUNNING:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task is already running.");
+ case FINISHED:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task has already been executed "
+ + "(a task can be executed only once)");
+ }
+ }
+ mStatus = Status.RUNNING;
+ try {
+ onPreExecute();
+ result = doInBackground(params);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!canceled) {
+ onPostExecute(result);
+ mStatus = Status.FINISHED;
+ } else {
+ onCancelled();
+ }
+ }
+ });
+ }
+ }
+
+ public void execute(Params... params) {
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ executorService.submit(new ProgressRunnable(params));
+ }
+
+ protected void onPreExecute() {
+ }
+
+ protected abstract Result doInBackground(Params... params);
+
+ protected void onPostExecute(Result result) {
+ }
+
+ public void cancel(boolean flag) {
+ canceled = flag;
+ }
+
+ public final boolean isCancelled() {
+ return canceled;
+ }
+
+ protected void onCancelled() {
+ }
+
+ public enum Status { PENDING, RUNNING, FINISHED }
+}
\ No newline at end of file
diff --git a/android_app/app/src/main/java/com/tun2http/app/utils/Util.java b/android_app/app/src/main/java/tun/utils/Util.java
similarity index 98%
rename from android_app/app/src/main/java/com/tun2http/app/utils/Util.java
rename to android_app/app/src/main/java/tun/utils/Util.java
index ffe3b8d..d595a34 100644
--- a/android_app/app/src/main/java/com/tun2http/app/utils/Util.java
+++ b/android_app/app/src/main/java/tun/utils/Util.java
@@ -1,4 +1,4 @@
-package com.tun2http.app.utils;
+package tun.utils;
/*
This file is part of NetGuard.
diff --git a/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
index c3903ed..2b068d1 100644
--- a/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/android_app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -1,34 +1,30 @@
-
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+
+ android:offset="0.0" />
+ android:offset="1.0" />
-
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/drawable/ic_launcher_background.xml b/android_app/app/src/main/res/drawable/ic_launcher_background.xml
index 5713f34..07d5da9 100644
--- a/android_app/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/android_app/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,171 +1,170 @@
-
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
diff --git a/android_app/app/src/main/res/layout/activity_main.xml b/android_app/app/src/main/res/layout/activity_main.xml
index 8ed0792..9ea1c52 100644
--- a/android_app/app/src/main/res/layout/activity_main.xml
+++ b/android_app/app/src/main/res/layout/activity_main.xml
@@ -1,33 +1,26 @@
-
+ tools:context=".MainActivity">
-
+ android:theme="@style/AppTheme.AppBarOverlay">
-
+
-
+
+
+
-
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/activity_settings.xml b/android_app/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..187565d
--- /dev/null
+++ b/android_app/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/content_main.xml b/android_app/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..34c86a4
--- /dev/null
+++ b/android_app/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android_app/app/src/main/res/layout/preference_progress.xml b/android_app/app/src/main/res/layout/preference_progress.xml
new file mode 100644
index 0000000..07c3cde
--- /dev/null
+++ b/android_app/app/src/main/res/layout/preference_progress.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/menu/menu_main.xml b/android_app/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..c578661
--- /dev/null
+++ b/android_app/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,14 @@
+
diff --git a/android_app/app/src/main/res/menu/menu_search.xml b/android_app/app/src/main/res/menu/menu_search.xml
new file mode 100644
index 0000000..07c926a
--- /dev/null
+++ b/android_app/app/src/main/res/menu/menu_search.xml
@@ -0,0 +1,104 @@
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index bbd3e02..c4a603d 100644
--- a/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index bbd3e02..c4a603d 100644
--- a/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/android_app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher.png
index a2f5908..ef23354 100644
Binary files a/android_app/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..3e61e3e
Binary files /dev/null and b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
index 1b52399..211c2f8 100644
Binary files a/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android_app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android_app/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher.png
index ff10afd..b1dc259 100644
Binary files a/android_app/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..c419935
Binary files /dev/null and b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
index 115a4c7..5869e1b 100644
Binary files a/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android_app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index dcd3cd8..cfe7d98 100644
Binary files a/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..3f2c205
Binary files /dev/null and b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
index 459ca60..1fe8053 100644
Binary files a/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android_app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 8ca12fe..69e1e60 100644
Binary files a/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..17e82c4
Binary files /dev/null and b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
index 8e19b41..99f53c5 100644
Binary files a/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android_app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index b824ebd..6849686 100644
Binary files a/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..7c45775
Binary files /dev/null and b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
index 4c19a13..931d615 100644
Binary files a/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android_app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android_app/app/src/main/res/navigation/nav_graph.xml b/android_app/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..d7fcbe4
--- /dev/null
+++ b/android_app/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/android_app/app/src/main/res/values/colors.xml b/android_app/app/src/main/res/values/colors.xml
index 3ab3e9c..030098f 100644
--- a/android_app/app/src/main/res/values/colors.xml
+++ b/android_app/app/src/main/res/values/colors.xml
@@ -1,6 +1,6 @@
- #3F51B5
- #303F9F
- #FF4081
+ #6200EE
+ #3700B3
+ #03DAC5
diff --git a/android_app/app/src/main/res/values/dimens.xml b/android_app/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/android_app/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml
index 407964b..0736104 100644
--- a/android_app/app/src/main/res/values/strings.xml
+++ b/android_app/app/src/main/res/values/strings.xml
@@ -1,10 +1,35 @@
- tun2http
+ TunProxy
+
+
Proxy is running
Proxy is running
Start
Stop
Start failed
- Proxy address (host:port)
+ Proxy address (ipv4:port)
Enter valid ip v4 address
+
+
+ About
+ Settings
+
+
+ Search
+
+ Allowed application list
+ Disallowed application list
+ Clear all selections of Allowed / Disallowed application list
+
+ Allowed / Disallowed Application
+ Prease wait
+
+ - Disallowed Application
+ - Allowed Application
+
+
+ - DISALLOW
+ - ALLOW
+
+
diff --git a/android_app/app/src/main/res/values/styles.xml b/android_app/app/src/main/res/values/styles.xml
index 5885930..083c770 100644
--- a/android_app/app/src/main/res/values/styles.xml
+++ b/android_app/app/src/main/res/values/styles.xml
@@ -8,4 +8,12 @@
- @color/colorAccent
+
+
+
+
+
diff --git a/android_app/app/src/main/res/xml/network_security_config.xml b/android_app/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..bb6ab93
--- /dev/null
+++ b/android_app/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/xml/preferences.xml b/android_app/app/src/main/res/xml/preferences.xml
new file mode 100644
index 0000000..4d835e7
--- /dev/null
+++ b/android_app/app/src/main/res/xml/preferences.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/test/java/com/tun2http/app/ExampleUnitTest.java b/android_app/app/src/test/java/com/tun2http/app/ExampleUnitTest.java
deleted file mode 100644
index ea8d45d..0000000
--- a/android_app/app/src/test/java/com/tun2http/app/ExampleUnitTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.tun2http.app;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file
diff --git a/android_app/app/src/test/java/tun/proxy/IPUtilTest.java b/android_app/app/src/test/java/tun/proxy/IPUtilTest.java
new file mode 100644
index 0000000..713fe88
--- /dev/null
+++ b/android_app/app/src/test/java/tun/proxy/IPUtilTest.java
@@ -0,0 +1,31 @@
+package tun.proxy;
+
+import org.junit.Test;
+
+import tun.utils.IPUtil;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class IPUtilTest {
+ @Test
+ public void testIsValidIPv4Address() {
+ assertEquals(IPUtil.isValidIPv4Address(""), false);
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.1"), true);
+ // 1 <= port <= 65535
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.1:0"), false);
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.1:1"), true);
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.1:65535"), true);
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.1:65536"), false);
+ assertEquals(IPUtil.isValidIPv4Address("127:8000"), false);
+ assertEquals(IPUtil.isValidIPv4Address("127.0:8000"), false);
+ assertEquals(IPUtil.isValidIPv4Address("127.0.0.0.1:8000"), false);
+ assertEquals(IPUtil.isValidIPv4Address("127.255.0.1:8000"), true);
+ assertEquals(IPUtil.isValidIPv4Address("127.256.0.1:8000"), false);
+ assertEquals(IPUtil.isValidIPv4Address("www.example.com:8000"), false);
+ }
+}
\ No newline at end of file
diff --git a/android_app/build.gradle b/android_app/build.gradle
index 020eae2..6099664 100644
--- a/android_app/build.gradle
+++ b/android_app/build.gradle
@@ -1,27 +1,54 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
-
+
repositories {
google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.0.0'
+ mavenCentral()
+ }
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.0.2'
}
}
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
+
+ }
+
+ gradle.projectsEvaluated {
+ tasks.withType(JavaCompile) {
+ options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+ }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+
+task release(type: Zip) {
+ archiveBaseName ="${APP_NAME}_v${VERSION_NAME}"
+ destinationDirectory = file("${projectDir}")
+ from rootProject.rootDir
+ include '*'
+ include 'gradle/**'
+ include 'images/**'
+ include 'app/**'
+ exclude 'build'
+ exclude 'app/.externalNativeBuild'
+ exclude 'app/.cxx'
+ exclude 'app/.gradle'
+ exclude 'app/.idea'
+ exclude 'app/libs'
+ exclude 'app/build'
+ exclude '.git'
+ exclude '.gradle'
+ exclude '.idea'
+ exclude 'local.properties'
+ exclude '*.zip'
+}
diff --git a/android_app/changeit.jks b/android_app/changeit.jks
new file mode 100644
index 0000000..fffb1ce
Binary files /dev/null and b/android_app/changeit.jks differ
diff --git a/android_app/gradle.properties b/android_app/gradle.properties
index aac7c9b..097b49f 100644
--- a/android_app/gradle.properties
+++ b/android_app/gradle.properties
@@ -1,17 +1,28 @@
# Project-wide Gradle settings.
-
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
-
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
-
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
-
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
+APP_NAME=TunProxy
+VERSION_NAME=1.2.8
+
+productKeyStore=../changeit.jks
+productKeyAlias=key0
+productKeyStorePassword=changeit
+productKeyAliasPassword=changeit
+
diff --git a/android_app/gradle/wrapper/gradle-wrapper.jar b/android_app/gradle/wrapper/gradle-wrapper.jar
index 13372ae..7f93135 100644
Binary files a/android_app/gradle/wrapper/gradle-wrapper.jar and b/android_app/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android_app/gradle/wrapper/gradle-wrapper.properties b/android_app/gradle/wrapper/gradle-wrapper.properties
index a0e2b78..3e59319 100644
--- a/android_app/gradle/wrapper/gradle-wrapper.properties
+++ b/android_app/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Thu Nov 09 17:21:37 GET 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/android_app/gradlew b/android_app/gradlew
index 9d82f78..cccdd3d 100644
--- a/android_app/gradlew
+++ b/android_app/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
##############################################################################
##
@@ -6,20 +6,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +48,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,26 +59,11 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/android_app/gradlew.bat b/android_app/gradlew.bat
index 8a0b282..f955316 100644
--- a/android_app/gradlew.bat
+++ b/android_app/gradlew.bat
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/android_app/settings.gradle b/android_app/settings.gradle
index e7b4def..806f720 100644
--- a/android_app/settings.gradle
+++ b/android_app/settings.gradle
@@ -1 +1,2 @@
+rootProject.name='TunProxy'
include ':app'
diff --git a/images/Menu-Settings-Menu.png b/images/Menu-Settings-Menu.png
new file mode 100644
index 0000000..d9ae3d6
Binary files /dev/null and b/images/Menu-Settings-Menu.png differ
diff --git a/images/Menu-Settings-Search.png b/images/Menu-Settings-Search.png
new file mode 100644
index 0000000..412bbcf
Binary files /dev/null and b/images/Menu-Settings-Search.png differ
diff --git a/images/Menu-Settings-app.png b/images/Menu-Settings-app.png
new file mode 100644
index 0000000..4012971
Binary files /dev/null and b/images/Menu-Settings-app.png differ
diff --git a/images/Menu-Settings.png b/images/Menu-Settings.png
new file mode 100644
index 0000000..172d3ff
Binary files /dev/null and b/images/Menu-Settings.png differ
diff --git a/images/Menu.png b/images/Menu.png
new file mode 100644
index 0000000..eed0670
Binary files /dev/null and b/images/Menu.png differ
diff --git a/images/Search.png b/images/Search.png
new file mode 100644
index 0000000..49fa080
Binary files /dev/null and b/images/Search.png differ
diff --git a/images/TunProxy.png b/images/TunProxy.png
new file mode 100644
index 0000000..7253db9
Binary files /dev/null and b/images/TunProxy.png differ