diff --git a/.gitignore b/.gitignore
index 1ea04a3..2b75303 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,13 @@
-build
-obj
-libs
-assets
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..ae78c11
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..d291b3d
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..29bb4c5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index b24678f..b98f329 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,10 @@
SocksDroid
----
-SOCKS5 client for Android 5.0+ making use of the `VpnService` API and `tun2socks` so that it works without root permission (unlike ProxyDroid).
+==========
-Most of the JNI code are imported from `shadowsocks-android` project because they have already done most of the work.
+## SOCKS5 client for Android 5.0+ using VpnService
-### THIS IS NOT A SHADOWSOCKS CLIENT! SOCKS5 IS NOT SHADOWSOCKS!
+This is an updated version of [SocksDroid by PeterCxy](https://github.com/PeterCxy/SocksDroid) to support modern Android devices.
-UDP Forwarding
----
-As `tun2socks` does not support UDP associate but has its own implementation of UDP forwarding `badvpn-udpgw`, so it is needed that the udpgw daemon run on remote server to use UDP forwarding.
+The project is in maintenance mode: no new features are planned, only bug fixes and compatibility upgrades.
-On remote server
-
-```
-badvpn-udpgw --listen-addr 127.0.0.1:7300
-```
-
-And set `UDP Gateway` in this app to `127.0.0.1:7300`
-
-DNS
----
-If the server does not run `udpgw`, DNS lookups can also be processed in this app.
-
-It makes use of the TCP DNS feature of `pdnsd`. You just set a DNS server that supports TCP DNS in this app, and all DNS requests will be transformed into TCP queries.
-
-Routing
----
-The app has an embedded list of non-Chinese IPs. Chinese users can make use of it for best experience in bypassing GFW.
-
-GFW
----
-Note that SOCKS5 is currently blocked by the GFW, which means Chinese users cannot connect to any SOCKS5 servers outside China.
-
-But there are still solutions. For example, use `stunnel` to wrap the SOCKS5 connection with SSL. See my project stunnel-android for usage on Android.
-
-License
----
-This project is licensed under GNU General Public License Version 3 or later.
+[](https://play.google.com/store/apps/details?id=net.typeblog.socks)
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
index da27d2a..4381400 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,24 +1,22 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 21
- buildToolsVersion "21.1.0"
-
+ compileSdkVersion 28
defaultConfig {
applicationId "net.typeblog.socks"
minSdkVersion 21
- targetSdkVersion 22
- versionCode 10
- versionName "1.0.1"
+ targetSdkVersion 28
+ versionCode 11
+ versionName "1.0.2"
}
buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
}
diff --git a/app/release/app-release.apk b/app/release/app-release.apk
new file mode 100644
index 0000000..48b5cdb
Binary files /dev/null and b/app/release/app-release.apk differ
diff --git a/app/release/output.json b/app/release/output.json
new file mode 100644
index 0000000..70c5a50
--- /dev/null
+++ b/app/release/output.json
@@ -0,0 +1 @@
+[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":11,"versionName":"1.0.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/app/src/main/.gitignore b/app/src/main/.gitignore
new file mode 100644
index 0000000..b672fde
--- /dev/null
+++ b/app/src/main/.gitignore
@@ -0,0 +1 @@
+obj
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1eba774..9a9041f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,15 +1,21 @@
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ package="net.typeblog.socks"
+ tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
+
+
+
+
+ android:theme="@style/AppTheme"
+ android:extractNativeLibs="false"
+ android:fullBackupContent="@xml/backup_descriptor">
@@ -19,22 +25,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/arm64-v8a/pdnsd b/app/src/main/assets/arm64-v8a/pdnsd
new file mode 100755
index 0000000..81f14d6
Binary files /dev/null and b/app/src/main/assets/arm64-v8a/pdnsd differ
diff --git a/app/src/main/assets/arm64-v8a/tun2socks b/app/src/main/assets/arm64-v8a/tun2socks
new file mode 100755
index 0000000..e5a5c1c
Binary files /dev/null and b/app/src/main/assets/arm64-v8a/tun2socks differ
diff --git a/app/src/main/assets/armeabi-v7a/pdnsd b/app/src/main/assets/armeabi-v7a/pdnsd
new file mode 100755
index 0000000..83f36c1
Binary files /dev/null and b/app/src/main/assets/armeabi-v7a/pdnsd differ
diff --git a/app/src/main/assets/armeabi-v7a/tun2socks b/app/src/main/assets/armeabi-v7a/tun2socks
new file mode 100755
index 0000000..a3abf29
Binary files /dev/null and b/app/src/main/assets/armeabi-v7a/tun2socks differ
diff --git a/app/src/main/assets/x86/pdnsd b/app/src/main/assets/x86/pdnsd
new file mode 100755
index 0000000..8e26523
Binary files /dev/null and b/app/src/main/assets/x86/pdnsd differ
diff --git a/app/src/main/assets/x86/tun2socks b/app/src/main/assets/x86/tun2socks
new file mode 100755
index 0000000..a6fd9b0
Binary files /dev/null and b/app/src/main/assets/x86/tun2socks differ
diff --git a/app/src/main/assets/x86_64/pdnsd b/app/src/main/assets/x86_64/pdnsd
new file mode 100755
index 0000000..ed983b3
Binary files /dev/null and b/app/src/main/assets/x86_64/pdnsd differ
diff --git a/app/src/main/assets/x86_64/tun2socks b/app/src/main/assets/x86_64/tun2socks
new file mode 100755
index 0000000..8ac36b8
Binary files /dev/null and b/app/src/main/assets/x86_64/tun2socks differ
diff --git a/app/src/main/build-jni.sh b/app/src/main/build-jni.sh
index 691d00b..f50156b 100755
--- a/app/src/main/build-jni.sh
+++ b/app/src/main/build-jni.sh
@@ -1,14 +1,14 @@
#!/bin/bash
rm -rf assets
-rm -rf ../../libs
+rm -rf jniLibs
ndk-build
-for p in armeabi-v7a arm64-v8a x86 mips; do
+for p in armeabi-v7a arm64-v8a x86 x86_64; do
mkdir -p assets/$p
cp libs/$p/{tun2socks,pdnsd} assets/$p/
done
rm -rf libs/*/{tun2socks,pdnsd}
-mv libs ../../
+mv libs jniLibs
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..763cdaa
Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ
diff --git a/app/src/main/java/net/typeblog/socks/BootReceiver.java b/app/src/main/java/net/typeblog/socks/BootReceiver.java
index 6206d80..e1b6e8f 100644
--- a/app/src/main/java/net/typeblog/socks/BootReceiver.java
+++ b/app/src/main/java/net/typeblog/socks/BootReceiver.java
@@ -12,19 +12,21 @@
import static net.typeblog.socks.BuildConfig.DEBUG;
public class BootReceiver extends BroadcastReceiver {
- private static final String TAG = BootReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Profile p = ProfileManager.getInstance(context).getDefault();
-
- if (p.autoConnect() && VpnService.prepare(context) == null) {
-
- if (DEBUG) {
- Log.d(TAG, "starting VPN service on boot");
- }
-
- Utility.startVpn(context, p);
- }
- }
+ private static final String TAG = BootReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ Profile p = new ProfileManager(context).getDefault();
+
+ if (p.autoConnect() && VpnService.prepare(context) == null) {
+
+ if (DEBUG) {
+ Log.d(TAG, "starting VPN service on boot");
+ }
+
+ Utility.startVpn(context, p);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/MainActivity.java b/app/src/main/java/net/typeblog/socks/MainActivity.java
index 9b3ff51..4481b7a 100644
--- a/app/src/main/java/net/typeblog/socks/MainActivity.java
+++ b/app/src/main/java/net/typeblog/socks/MainActivity.java
@@ -1,46 +1,17 @@
package net.typeblog.socks;
import android.app.Activity;
-import android.content.Intent;
-import android.net.VpnService;
import android.os.Bundle;
import net.typeblog.socks.util.Utility;
-import static net.typeblog.socks.util.Constants.*;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- Utility.extractFile(this);
-
- getFragmentManager().beginTransaction().replace(R.id.frame, new ProfileFragment()).commit();
- }
+ super.onCreate(savedInstanceState);
- /*@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode == RESULT_OK) {
- Intent i = new Intent(this, SocksVpnService.class)
- .putExtra(INTENT_NAME, "test")
- .putExtra(INTENT_SERVER, "127.0.0.1")
- .putExtra(INTENT_PORT, 2352)
-
- ;
-
- startService(i);
- }
- }
-
- private void startVpn() {
- Intent i = VpnService.prepare(this);
- if (i != null) {
- startActivityForResult(i, 0);
- } else {
- onActivityResult(0, RESULT_OK, null);
- }
- }*/
+ Utility.extractFile(this);
+
+ this.getFragmentManager().beginTransaction().replace(android.R.id.content, new ProfileFragment()).commit();
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/ProfileFragment.java b/app/src/main/java/net/typeblog/socks/ProfileFragment.java
index 6295b8d..ff34d8a 100644
--- a/app/src/main/java/net/typeblog/socks/ProfileFragment.java
+++ b/app/src/main/java/net/typeblog/socks/ProfileFragment.java
@@ -27,410 +27,417 @@
import net.typeblog.socks.util.Profile;
import net.typeblog.socks.util.ProfileManager;
import net.typeblog.socks.util.Utility;
+
+import java.util.Locale;
+
import static net.typeblog.socks.util.Constants.*;
public class ProfileFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener,
- CompoundButton.OnCheckedChangeListener {
- private ProfileManager mManager;
- private Profile mProfile;
-
- private Switch mSwitch;
- private boolean mRunning = false;
- private boolean mStarting = false, mStopping = false;
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName p1, IBinder binder) {
- mBinder = IVpnService.Stub.asInterface(binder);
-
- try {
- mRunning = mBinder.isRunning();
- } catch (Exception e) {
-
- }
-
- if (mRunning) {
- updateState();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName p1) {
- mBinder = null;
- }
- };
- private Runnable mStateRunnable = new Runnable() {
- @Override
- public void run() {
- updateState();
- mSwitch.postDelayed(this, 1000);
- }
- };
- private IVpnService mBinder;
-
- private ListPreference mPrefProfile, mPrefRoutes;
- private EditTextPreference mPrefServer, mPrefPort, mPrefUsername, mPrefPassword,
- mPrefDns, mPrefDnsPort, mPrefAppList, mPrefUDPGW;
- private CheckBoxPreference mPrefUserpw, mPrefPerApp, mPrefAppBypass, mPrefIPv6, mPrefUDP, mPrefAuto;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.settings);
- setHasOptionsMenu(true);
- mManager = ProfileManager.getInstance(getActivity().getApplicationContext());
- initPreferences();
- reload();
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.main, menu);
-
- MenuItem s = menu.findItem(R.id.switch_main);
- mSwitch = (Switch) s.getActionView().findViewById(R.id.switch_action_button);
- mSwitch.setOnCheckedChangeListener(this);
- mSwitch.postDelayed(mStateRunnable, 1000);
- checkState();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.prof_add:
- addProfile();
- return true;
- case R.id.prof_del:
- removeProfile();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public boolean onPreferenceClick(Preference p) {
- // TODO: Implement this method
- return false;
- }
-
- @Override
- public boolean onPreferenceChange(Preference p, Object newValue) {
- if (p == mPrefProfile) {
- String name = newValue.toString();
- mProfile = mManager.getProfile(name);
- mManager.switchDefault(name);
- reload();
- return true;
- } else if (p == mPrefServer) {
- mProfile.setServer(newValue.toString());
- resetTextN(mPrefServer, newValue);
- return true;
- } else if (p == mPrefPort) {
- if (TextUtils.isEmpty(newValue.toString()))
- return false;
-
- mProfile.setPort(Integer.parseInt(newValue.toString()));
- resetTextN(mPrefPort, newValue);
- return true;
- } else if (p == mPrefUserpw) {
- mProfile.setIsUserpw(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else if (p == mPrefUsername) {
- mProfile.setUsername(newValue.toString());
- resetTextN(mPrefUsername, newValue);
- return true;
- } else if (p == mPrefPassword) {
- mProfile.setPassword(newValue.toString());
- resetTextN(mPrefPassword, newValue);
- return true;
- } else if (p == mPrefRoutes) {
- mProfile.setRoute(newValue.toString());
- resetListN(mPrefRoutes, newValue);
- return true;
- } else if (p == mPrefDns) {
- mProfile.setDns(newValue.toString());
- resetTextN(mPrefDns, newValue);
- return true;
- } else if (p == mPrefDnsPort) {
- if (TextUtils.isEmpty(newValue.toString()))
- return false;
-
- mProfile.setDnsPort(Integer.valueOf(newValue.toString()));
- resetTextN(mPrefDnsPort, newValue);
- return true;
- } else if (p == mPrefPerApp) {
- mProfile.setIsPerApp(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else if (p == mPrefAppBypass) {
- mProfile.setIsBypassApp(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else if (p == mPrefAppList) {
- mProfile.setAppList(newValue.toString());
- return true;
- } else if (p == mPrefIPv6) {
- mProfile.setHasIPv6(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else if (p == mPrefUDP) {
- mProfile.setHasUDP(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else if (p == mPrefUDPGW) {
- mProfile.setUDPGW(newValue.toString());
- resetTextN(mPrefUDPGW, newValue);
- return true;
- } else if (p == mPrefAuto) {
- mProfile.setAutoConnect(Boolean.parseBoolean(newValue.toString()));
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void onCheckedChanged(CompoundButton p1, boolean checked) {
- if (checked) {
- startVpn();
- } else {
- stopVpn();
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode == Activity.RESULT_OK) {
- Utility.startVpn(getActivity(), mProfile);
- checkState();
- }
- }
-
- private void initPreferences() {
- mPrefProfile = (ListPreference) findPreference(PREF_PROFILE);
- mPrefServer = (EditTextPreference) findPreference(PREF_SERVER_IP);
- mPrefPort = (EditTextPreference) findPreference(PREF_SERVER_PORT);
- mPrefUserpw = (CheckBoxPreference) findPreference(PREF_AUTH_USERPW);
- mPrefUsername = (EditTextPreference) findPreference(PREF_AUTH_USERNAME);
- mPrefPassword = (EditTextPreference) findPreference(PREF_AUTH_PASSWORD);
- mPrefRoutes = (ListPreference) findPreference(PREF_ADV_ROUTE);
- mPrefDns = (EditTextPreference) findPreference(PREF_ADV_DNS);
- mPrefDnsPort = (EditTextPreference) findPreference(PREF_ADV_DNS_PORT);
- mPrefPerApp = (CheckBoxPreference) findPreference(PREF_ADV_PER_APP);
- mPrefAppBypass = (CheckBoxPreference) findPreference(PREF_ADV_APP_BYPASS);
- mPrefAppList = (EditTextPreference) findPreference(PREF_ADV_APP_LIST);
- mPrefIPv6 = (CheckBoxPreference) findPreference(PREF_IPV6_PROXY);
- mPrefUDP = (CheckBoxPreference) findPreference(PREF_UDP_PROXY);
- mPrefUDPGW = (EditTextPreference) findPreference(PREF_UDP_GW);
- mPrefAuto = (CheckBoxPreference) findPreference(PREF_ADV_AUTO_CONNECT);
-
- mPrefProfile.setOnPreferenceChangeListener(this);
- mPrefServer.setOnPreferenceChangeListener(this);
- mPrefPort.setOnPreferenceChangeListener(this);
- mPrefUserpw.setOnPreferenceChangeListener(this);
- mPrefUsername.setOnPreferenceChangeListener(this);
- mPrefPassword.setOnPreferenceChangeListener(this);
- mPrefRoutes.setOnPreferenceChangeListener(this);
- mPrefDns.setOnPreferenceChangeListener(this);
- mPrefDnsPort.setOnPreferenceChangeListener(this);
- mPrefPerApp.setOnPreferenceChangeListener(this);
- mPrefAppBypass.setOnPreferenceChangeListener(this);
- mPrefAppList.setOnPreferenceChangeListener(this);
- mPrefIPv6.setOnPreferenceChangeListener(this);
- mPrefUDP.setOnPreferenceChangeListener(this);
- mPrefUDPGW.setOnPreferenceChangeListener(this);
- mPrefAuto.setOnPreferenceChangeListener(this);
- }
-
- private void reload() {
- if (mProfile == null) {
- mProfile = mManager.getDefault();
- }
-
- mPrefProfile.setEntries(mManager.getProfiles());
- mPrefProfile.setEntryValues(mManager.getProfiles());
- mPrefProfile.setValue(mProfile.getName());
- mPrefRoutes.setValue(mProfile.getRoute());
- resetList(mPrefProfile, mPrefRoutes);
-
- mPrefUserpw.setChecked(mProfile.isUserPw());
- mPrefPerApp.setChecked(mProfile.isPerApp());
- mPrefAppBypass.setChecked(mProfile.isBypassApp());
- mPrefIPv6.setChecked(mProfile.hasIPv6());
- mPrefUDP.setChecked(mProfile.hasUDP());
- mPrefAuto.setChecked(mProfile.autoConnect());
-
- mPrefServer.setText(mProfile.getServer());
- mPrefPort.setText(String.valueOf(mProfile.getPort()));
- mPrefUsername.setText(mProfile.getUsername());
- mPrefPassword.setText(mProfile.getPassword());
- mPrefDns.setText(mProfile.getDns());
- mPrefDnsPort.setText(String.valueOf(mProfile.getDnsPort()));
- mPrefUDPGW.setText(mProfile.getUDPGW());
- resetText(mPrefServer, mPrefPort, mPrefUsername, mPrefPassword, mPrefDns, mPrefDnsPort, mPrefUDPGW);
-
- mPrefAppList.setText(mProfile.getAppList());
- }
-
- private void resetList(ListPreference... pref) {
- for (ListPreference p : pref)
- p.setSummary(p.getEntry());
- }
-
- private void resetListN(ListPreference pref, Object newValue) {
- pref.setSummary(newValue.toString());
- }
-
- private void resetText(EditTextPreference... pref) {
- for (EditTextPreference p : pref) {
- if ((p.getEditText().getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) != InputType.TYPE_TEXT_VARIATION_PASSWORD) {
- p.setSummary(p.getText());
- } else {
- if (p.getText().length() > 0)
- p.setSummary(String.format(String.format("%%0%dd", p.getText().length()), 0).replace("0", "*"));
- else
- p.setSummary("");
- }
- }
- }
-
- private void resetTextN(EditTextPreference pref, Object newValue) {
- if ((pref.getEditText().getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) != InputType.TYPE_TEXT_VARIATION_PASSWORD) {
- pref.setSummary(newValue.toString());
- } else {
- String text = newValue.toString();
- if (text.length() > 0)
- pref.setSummary(String.format(String.format("%%0%dd", text.length()), 0).replace("0", "*"));
- else
- pref.setSummary("");
- }
- }
-
- private void addProfile() {
- final EditText e = new EditText(getActivity());
- e.setSingleLine(true);
-
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.prof_add)
- .setView(e)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
- String name = e.getText().toString().trim();
-
- if (!TextUtils.isEmpty(name)) {
- Profile p = mManager.addProfile(name);
-
- if (p != null) {
- mProfile = p;
- reload();
- return;
- }
- }
-
- Toast.makeText(getActivity(),
- String.format(getString(R.string.err_add_prof), name),
- Toast.LENGTH_SHORT).show();
- }
- })
- .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
-
- }
- })
- .create().show();
- }
-
- private void removeProfile() {
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.prof_del)
- .setMessage(String.format(getString(R.string.prof_del_confirm), mProfile.getName()))
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
- if (!mManager.removeProfile(mProfile.getName())) {
- Toast.makeText(getActivity(),
- getString(R.string.err_del_prof, mProfile.getName()),
- Toast.LENGTH_SHORT).show();
- } else {
- mProfile = mManager.getDefault();
- reload();
- }
- }
- })
- .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
-
- }
- })
- .create().show();
- }
-
- private void checkState() {
- mRunning = false;
- mSwitch.setEnabled(false);
- mSwitch.setOnCheckedChangeListener(null);
-
- if (mBinder == null) {
- getActivity().bindService(new Intent(getActivity(), SocksVpnService.class), mConnection, 0);
- }
- }
-
- private void updateState() {
- if (mBinder == null) {
- mRunning = false;
- } else {
- try {
- mRunning = mBinder.isRunning();
- } catch (Exception e) {
- mRunning = false;
- }
- }
-
- mSwitch.setChecked(mRunning);
-
- if ((!mStarting && !mStopping) || (mStarting && mRunning) || (mStopping && !mRunning)) {
- mSwitch.setEnabled(true);
- }
-
- if (mStarting && mRunning) {
- mStarting = false;
- }
-
- if (mStopping && !mRunning) {
- mStopping = false;
- }
-
- mSwitch.setOnCheckedChangeListener(ProfileFragment.this);
- }
-
- private void startVpn() {
- mStarting = true;
- Intent i = VpnService.prepare(getActivity());
-
- if (i != null) {
- startActivityForResult(i, 0);
- } else {
- onActivityResult(0, Activity.RESULT_OK, null);
- }
- }
-
- private void stopVpn() {
- if (mBinder == null)
- return;
-
- mStopping = true;
-
- try {
- mBinder.stop();
- } catch (Exception e) {
-
- }
-
- mBinder = null;
-
- getActivity().unbindService(mConnection);
- checkState();
- }
+ CompoundButton.OnCheckedChangeListener {
+ private ProfileManager mManager;
+ private Profile mProfile;
+
+ private Switch mSwitch;
+ private boolean mRunning = false;
+ private boolean mStarting = false, mStopping = false;
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName p1, IBinder binder) {
+ mBinder = IVpnService.Stub.asInterface(binder);
+
+ try {
+ mRunning = mBinder.isRunning();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ if (mRunning) {
+ updateState();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName p1) {
+ mBinder = null;
+ }
+ };
+ private final Runnable mStateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateState();
+ mSwitch.postDelayed(this, 1000);
+ }
+ };
+ private IVpnService mBinder;
+
+ private ListPreference mPrefProfile, mPrefRoutes;
+ private EditTextPreference mPrefServer, mPrefPort, mPrefUsername, mPrefPassword,
+ mPrefDns, mPrefDnsPort, mPrefAppList, mPrefUDPGW;
+ private CheckBoxPreference mPrefUserpw, mPrefPerApp, mPrefAppBypass, mPrefIPv6, mPrefUDP, mPrefAuto;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.settings);
+ setHasOptionsMenu(true);
+ mManager = new ProfileManager(getActivity().getApplicationContext());
+ initPreferences();
+ reload();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.main, menu);
+
+ MenuItem s = menu.findItem(R.id.switch_main);
+ mSwitch = s.getActionView().findViewById(R.id.switch_action_button);
+ mSwitch.setOnCheckedChangeListener(this);
+ mSwitch.postDelayed(mStateRunnable, 1000);
+ checkState();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.prof_add:
+ addProfile();
+ return true;
+ case R.id.prof_del:
+ removeProfile();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference p) {
+ // TODO: Implement this method
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference p, Object newValue) {
+ if (p == mPrefProfile) {
+ String name = newValue.toString();
+ mProfile = mManager.getProfile(name);
+ mManager.switchDefault(name);
+ reload();
+ return true;
+ } else if (p == mPrefServer) {
+ mProfile.setServer(newValue.toString());
+ resetTextN(mPrefServer, newValue);
+ return true;
+ } else if (p == mPrefPort) {
+ if (TextUtils.isEmpty(newValue.toString()))
+ return false;
+
+ mProfile.setPort(Integer.parseInt(newValue.toString()));
+ resetTextN(mPrefPort, newValue);
+ return true;
+ } else if (p == mPrefUserpw) {
+ mProfile.setIsUserpw(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else if (p == mPrefUsername) {
+ mProfile.setUsername(newValue.toString());
+ resetTextN(mPrefUsername, newValue);
+ return true;
+ } else if (p == mPrefPassword) {
+ mProfile.setPassword(newValue.toString());
+ resetTextN(mPrefPassword, newValue);
+ return true;
+ } else if (p == mPrefRoutes) {
+ mProfile.setRoute(newValue.toString());
+ resetListN(mPrefRoutes, newValue);
+ return true;
+ } else if (p == mPrefDns) {
+ mProfile.setDns(newValue.toString());
+ resetTextN(mPrefDns, newValue);
+ return true;
+ } else if (p == mPrefDnsPort) {
+ if (TextUtils.isEmpty(newValue.toString()))
+ return false;
+
+ mProfile.setDnsPort(Integer.valueOf(newValue.toString()));
+ resetTextN(mPrefDnsPort, newValue);
+ return true;
+ } else if (p == mPrefPerApp) {
+ mProfile.setIsPerApp(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else if (p == mPrefAppBypass) {
+ mProfile.setIsBypassApp(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else if (p == mPrefAppList) {
+ mProfile.setAppList(newValue.toString());
+ return true;
+ } else if (p == mPrefIPv6) {
+ mProfile.setHasIPv6(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else if (p == mPrefUDP) {
+ mProfile.setHasUDP(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else if (p == mPrefUDPGW) {
+ mProfile.setUDPGW(newValue.toString());
+ resetTextN(mPrefUDPGW, newValue);
+ return true;
+ } else if (p == mPrefAuto) {
+ mProfile.setAutoConnect(Boolean.parseBoolean(newValue.toString()));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton p1, boolean checked) {
+ if (checked) {
+ startVpn();
+ } else {
+ stopVpn();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (resultCode == Activity.RESULT_OK) {
+ Utility.startVpn(getActivity(), mProfile);
+ checkState();
+ }
+ }
+
+ private void initPreferences() {
+ mPrefProfile = (ListPreference) findPreference(PREF_PROFILE);
+ mPrefServer = (EditTextPreference) findPreference(PREF_SERVER_IP);
+ mPrefPort = (EditTextPreference) findPreference(PREF_SERVER_PORT);
+ mPrefUserpw = (CheckBoxPreference) findPreference(PREF_AUTH_USERPW);
+ mPrefUsername = (EditTextPreference) findPreference(PREF_AUTH_USERNAME);
+ mPrefPassword = (EditTextPreference) findPreference(PREF_AUTH_PASSWORD);
+ mPrefRoutes = (ListPreference) findPreference(PREF_ADV_ROUTE);
+ mPrefDns = (EditTextPreference) findPreference(PREF_ADV_DNS);
+ mPrefDnsPort = (EditTextPreference) findPreference(PREF_ADV_DNS_PORT);
+ mPrefPerApp = (CheckBoxPreference) findPreference(PREF_ADV_PER_APP);
+ mPrefAppBypass = (CheckBoxPreference) findPreference(PREF_ADV_APP_BYPASS);
+ mPrefAppList = (EditTextPreference) findPreference(PREF_ADV_APP_LIST);
+ mPrefIPv6 = (CheckBoxPreference) findPreference(PREF_IPV6_PROXY);
+ mPrefUDP = (CheckBoxPreference) findPreference(PREF_UDP_PROXY);
+ mPrefUDPGW = (EditTextPreference) findPreference(PREF_UDP_GW);
+ mPrefAuto = (CheckBoxPreference) findPreference(PREF_ADV_AUTO_CONNECT);
+
+ mPrefProfile.setOnPreferenceChangeListener(this);
+ mPrefServer.setOnPreferenceChangeListener(this);
+ mPrefPort.setOnPreferenceChangeListener(this);
+ mPrefUserpw.setOnPreferenceChangeListener(this);
+ mPrefUsername.setOnPreferenceChangeListener(this);
+ mPrefPassword.setOnPreferenceChangeListener(this);
+ mPrefRoutes.setOnPreferenceChangeListener(this);
+ mPrefDns.setOnPreferenceChangeListener(this);
+ mPrefDnsPort.setOnPreferenceChangeListener(this);
+ mPrefPerApp.setOnPreferenceChangeListener(this);
+ mPrefAppBypass.setOnPreferenceChangeListener(this);
+ mPrefAppList.setOnPreferenceChangeListener(this);
+ mPrefIPv6.setOnPreferenceChangeListener(this);
+ mPrefUDP.setOnPreferenceChangeListener(this);
+ mPrefUDPGW.setOnPreferenceChangeListener(this);
+ mPrefAuto.setOnPreferenceChangeListener(this);
+ }
+
+ private void reload() {
+ if (mProfile == null) {
+ mProfile = mManager.getDefault();
+ }
+
+ mPrefProfile.setEntries(mManager.getProfiles());
+ mPrefProfile.setEntryValues(mManager.getProfiles());
+ mPrefProfile.setValue(mProfile.getName());
+ mPrefRoutes.setValue(mProfile.getRoute());
+ resetList(mPrefProfile, mPrefRoutes);
+
+ mPrefUserpw.setChecked(mProfile.isUserPw());
+ mPrefPerApp.setChecked(mProfile.isPerApp());
+ mPrefAppBypass.setChecked(mProfile.isBypassApp());
+ mPrefIPv6.setChecked(mProfile.hasIPv6());
+ mPrefUDP.setChecked(mProfile.hasUDP());
+ mPrefAuto.setChecked(mProfile.autoConnect());
+
+ mPrefServer.setText(mProfile.getServer());
+ mPrefPort.setText(String.valueOf(mProfile.getPort()));
+ mPrefUsername.setText(mProfile.getUsername());
+ mPrefPassword.setText(mProfile.getPassword());
+ mPrefDns.setText(mProfile.getDns());
+ mPrefDnsPort.setText(String.valueOf(mProfile.getDnsPort()));
+ mPrefUDPGW.setText(mProfile.getUDPGW());
+ resetText(mPrefServer, mPrefPort, mPrefUsername, mPrefPassword, mPrefDns, mPrefDnsPort, mPrefUDPGW);
+
+ mPrefAppList.setText(mProfile.getAppList());
+ }
+
+ private void resetList(ListPreference... pref) {
+ for (ListPreference p : pref)
+ p.setSummary(p.getEntry());
+ }
+
+ private void resetListN(ListPreference pref, Object newValue) {
+ pref.setSummary(newValue.toString());
+ }
+
+ private void resetText(EditTextPreference... pref) {
+ for (EditTextPreference p : pref) {
+ if ((p.getEditText().getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) != InputType.TYPE_TEXT_VARIATION_PASSWORD) {
+ p.setSummary(p.getText());
+ } else {
+ if (p.getText().length() > 0)
+ p.setSummary(String.format(Locale.US,
+ String.format(Locale.US, "%%0%dd", p.getText().length()), 0)
+ .replace("0", "*"));
+ else
+ p.setSummary("");
+ }
+ }
+ }
+
+ private void resetTextN(EditTextPreference pref, Object newValue) {
+ if ((pref.getEditText().getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) != InputType.TYPE_TEXT_VARIATION_PASSWORD) {
+ pref.setSummary(newValue.toString());
+ } else {
+ String text = newValue.toString();
+ if (text.length() > 0)
+ pref.setSummary(String.format(Locale.US,
+ String.format(Locale.US, "%%0%dd", text.length()), 0)
+ .replace("0", "*"));
+ else
+ pref.setSummary("");
+ }
+ }
+
+ private void addProfile() {
+ final EditText e = new EditText(getActivity());
+ e.setSingleLine(true);
+
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.prof_add)
+ .setView(e)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface d, int which) {
+ String name = e.getText().toString().trim();
+
+ if (!TextUtils.isEmpty(name)) {
+ Profile p = mManager.addProfile(name);
+
+ if (p != null) {
+ mProfile = p;
+ reload();
+ return;
+ }
+ }
+
+ Toast.makeText(getActivity(),
+ String.format(getString(R.string.err_add_prof), name),
+ Toast.LENGTH_SHORT).show();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface d, int which) {
+
+ }
+ })
+ .create().show();
+ }
+
+ private void removeProfile() {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.prof_del)
+ .setMessage(String.format(getString(R.string.prof_del_confirm), mProfile.getName()))
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface d, int which) {
+ if (!mManager.removeProfile(mProfile.getName())) {
+ Toast.makeText(getActivity(),
+ getString(R.string.err_del_prof, mProfile.getName()),
+ Toast.LENGTH_SHORT).show();
+ } else {
+ mProfile = mManager.getDefault();
+ reload();
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface d, int which) {
+
+ }
+ })
+ .create().show();
+ }
+
+ private void checkState() {
+ mRunning = false;
+ mSwitch.setEnabled(false);
+ mSwitch.setOnCheckedChangeListener(null);
+
+ if (mBinder == null) {
+ getActivity().bindService(new Intent(getActivity(), SocksVpnService.class), mConnection, 0);
+ }
+ }
+
+ private void updateState() {
+ if (mBinder == null) {
+ mRunning = false;
+ } else {
+ try {
+ mRunning = mBinder.isRunning();
+ } catch (Exception e) {
+ mRunning = false;
+ }
+ }
+
+ mSwitch.setChecked(mRunning);
+
+ if ((!mStarting && !mStopping) || (mStarting && mRunning) || (mStopping && !mRunning)) {
+ mSwitch.setEnabled(true);
+ }
+
+ if (mStarting && mRunning) {
+ mStarting = false;
+ }
+
+ if (mStopping && !mRunning) {
+ mStopping = false;
+ }
+
+ mSwitch.setOnCheckedChangeListener(ProfileFragment.this);
+ }
+
+ private void startVpn() {
+ mStarting = true;
+ Intent i = VpnService.prepare(getActivity());
+
+ if (i != null) {
+ startActivityForResult(i, 0);
+ } else {
+ onActivityResult(0, Activity.RESULT_OK, null);
+ }
+ }
+
+ private void stopVpn() {
+ if (mBinder == null)
+ return;
+
+ mStopping = true;
+
+ try {
+ mBinder.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ mBinder = null;
+
+ getActivity().unbindService(mConnection);
+ checkState();
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/SocksVpnService.java b/app/src/main/java/net/typeblog/socks/SocksVpnService.java
index d181464..636acc6 100644
--- a/app/src/main/java/net/typeblog/socks/SocksVpnService.java
+++ b/app/src/main/java/net/typeblog/socks/SocksVpnService.java
@@ -1,9 +1,13 @@
package net.typeblog.socks;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Intent;
import android.net.VpnService;
import android.net.VpnService.Builder;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -12,234 +16,254 @@
import net.typeblog.socks.R;
import net.typeblog.socks.util.Routes;
import net.typeblog.socks.util.Utility;
+
+import java.util.Locale;
+
import static net.typeblog.socks.util.Constants.*;
import static net.typeblog.socks.BuildConfig.DEBUG;
public class SocksVpnService extends VpnService {
- class VpnBinder extends IVpnService.Stub {
- @Override
- public boolean isRunning() {
- return mRunning;
- }
-
- @Override
- public void stop() {
- stopMe();
- }
- }
-
- private static final String TAG = SocksVpnService.class.getSimpleName();
-
- private ParcelFileDescriptor mInterface;
- private boolean mRunning = false;
- private IBinder mBinder = new VpnBinder();
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
-
- if (DEBUG) {
- Log.d(TAG, "starting");
- }
-
- if (intent == null) {
- return 0;
- }
-
- if (mRunning == true) {
- return 0;
- }
-
- final String name = intent.getStringExtra(INTENT_NAME);
- final String server = intent.getStringExtra(INTENT_SERVER);
- final int port = intent.getIntExtra(INTENT_PORT, 1080);
- final String username = intent.getStringExtra(INTENT_USERNAME);
- final String passwd = intent.getStringExtra(INTENT_PASSWORD);
- final String route = intent.getStringExtra(INTENT_ROUTE);
- final String dns = intent.getStringExtra(INTENT_DNS);
- final int dnsPort = intent.getIntExtra(INTENT_DNS_PORT, 53);
- final boolean perApp = intent.getBooleanExtra(INTENT_PER_APP, false);
- final boolean appBypass = intent.getBooleanExtra(INTENT_APP_BYPASS, false);
- final String[] appList = intent.getStringArrayExtra(INTENT_APP_LIST);
- final boolean ipv6 = intent.getBooleanExtra(INTENT_IPV6_PROXY, false);
- final String udpgw = intent.getStringExtra(INTENT_UDP_GW);
-
- // Create the notification
- startForeground(R.drawable.ic_launcher,
- new Notification.Builder(this)
- .setContentTitle(getString(R.string.notify_title))
- .setContentText(String.format(getString(R.string.notify_msg), name))
- .setPriority(Notification.PRIORITY_MIN)
- .setSmallIcon(android.R.color.transparent)
- .build());
-
- // Create an fd.
- configure(name, route, perApp, appBypass, appList, ipv6);
-
- if (DEBUG)
- Log.d(TAG, "fd: " + mInterface.getFd());
-
- if (mInterface != null)
- start(mInterface.getFd(), server, port, username, passwd, dns, dnsPort, ipv6, udpgw);
-
- return START_STICKY;
- }
-
- @Override
- public void onRevoke() {
- super.onRevoke();
- stopMe();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- stopMe();
- }
-
- private void stopMe() {
- stopForeground(true);
-
- Utility.killPidFile(DIR + "/tun2socks.pid");
- Utility.killPidFile(DIR + "/pdnsd.pid");
-
- try {
- System.jniclose(mInterface.getFd());
- mInterface.close();
- } catch (Exception e) {
- }
-
- stopSelf();
- }
-
- private void configure(String name, String route, boolean perApp, boolean bypass, String[] apps, boolean ipv6) {
- Builder b = new Builder();
- b.setMtu(1500)
- .setSession(name)
- .addAddress("26.26.26.1", 24)
- .addDnsServer("8.8.8.8");
-
- if (ipv6) {
- // Route all IPv6 traffic
- b.addAddress("fdfe:dcba:9876::1", 126)
- .addRoute("::", 0);
- }
-
- Routes.addRoutes(this, b, route);
-
- // Add the default DNS
- // Note that this DNS is just a stub.
- // Actual DNS requests will be redirected through pdnsd.
- b.addRoute("8.8.8.8", 32);
-
- // Do app routing
- if (!perApp) {
- // Just bypass myself
- try {
- b.addDisallowedApplication("net.typeblog.socks");
- } catch (Exception e) {
-
- }
- } else {
- if (bypass) {
- // First, bypass myself
- try {
- b.addDisallowedApplication("net.typeblog.socks");
- } catch (Exception e) {
-
- }
-
- for (String p : apps) {
- if (TextUtils.isEmpty(p))
- continue;
-
- try {
- b.addDisallowedApplication(p.trim());
- } catch (Exception e) {
-
- }
- }
- } else {
- for (String p : apps) {
- if (TextUtils.isEmpty(p) || p.trim().equals("net.typeblog.socks")) {
- continue;
- }
-
- try {
- b.addAllowedApplication(p.trim());
- } catch (Exception e) {
-
- }
- }
- }
- }
-
- mInterface = b.establish();
- }
-
- private void start(int fd, String server, int port, String user, String passwd, String dns, int dnsPort, boolean ipv6, String udpgw) {
- // Start DNS daemon first
- Utility.makePdnsdConf(this, dns, dnsPort);
-
- Utility.exec(String.format("%s/pdnsd -c %s/pdnsd.conf", DIR, DIR));
-
- String command = String.format(
- "%s/tun2socks --netif-ipaddr 26.26.26.2"
- + " --netif-netmask 255.255.255.0"
- + " --socks-server-addr %s:%d"
- + " --tunfd %d"
- + " --tunmtu 1500"
- + " --loglevel 3"
- + " --pid %s/tun2socks.pid"
- , DIR, server, port, fd, DIR);
-
- if (user != null) {
- command += " --username " + user;
- command += " --password " + passwd;
- }
-
- if (ipv6) {
- command += " --netif-ip6addr fdfe:dcba:9876::2";
- }
-
- command += " --dnsgw 26.26.26.1:8091";
-
- if (udpgw != null) {
- command += " --udpgw-remote-server-addr " + udpgw;
- }
-
- if (DEBUG) {
- Log.d(TAG, command);
- }
-
- if (Utility.exec(command) != 0) {
- stopMe();
- return;
- }
-
- // Try to send the Fd through socket.
- int i = 0;
- while (i < 5) {
- if (System.sendfd(fd) != -1) {
- mRunning = true;
- return;
- }
-
- i++;
-
- try {
- Thread.sleep(1000 * i);
- } catch (Exception e) {
-
- }
- }
-
- // Should not get here. Must be a failure.
- stopMe();
- }
+ class VpnBinder extends IVpnService.Stub {
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public void stop() {
+ stopMe();
+ }
+ }
+
+ private static final String TAG = SocksVpnService.class.getSimpleName();
+
+ private ParcelFileDescriptor mInterface;
+ private boolean mRunning = false;
+ private final IBinder mBinder = new VpnBinder();
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ if (DEBUG) {
+ Log.d(TAG, "starting");
+ }
+
+ if (intent == null) {
+ return START_STICKY;
+ }
+
+ if (mRunning) {
+ return START_STICKY;
+ }
+
+ final String name = intent.getStringExtra(INTENT_NAME);
+ final String server = intent.getStringExtra(INTENT_SERVER);
+ final int port = intent.getIntExtra(INTENT_PORT, 1080);
+ final String username = intent.getStringExtra(INTENT_USERNAME);
+ final String passwd = intent.getStringExtra(INTENT_PASSWORD);
+ final String route = intent.getStringExtra(INTENT_ROUTE);
+ final String dns = intent.getStringExtra(INTENT_DNS);
+ final int dnsPort = intent.getIntExtra(INTENT_DNS_PORT, 53);
+ final boolean perApp = intent.getBooleanExtra(INTENT_PER_APP, false);
+ final boolean appBypass = intent.getBooleanExtra(INTENT_APP_BYPASS, false);
+ final String[] appList = intent.getStringArrayExtra(INTENT_APP_LIST);
+ final boolean ipv6 = intent.getBooleanExtra(INTENT_IPV6_PROXY, false);
+ final String udpgw = intent.getStringExtra(INTENT_UDP_GW);
+
+ // Notifications on Oreo and above need a channel
+ Notification.Builder builder;
+ if (Build.VERSION.SDK_INT >= 26) {
+ String NOTIFICATION_CHANNEL_ID = "net.typeblog.socks";
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ getString(R.string.channel_name), NotificationManager.IMPORTANCE_NONE);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID);
+ } else {
+ builder = new Notification.Builder(this);
+ }
+
+ // Create the notification
+ int NOTIFICATION_ID = 1;
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
+ startForeground(NOTIFICATION_ID, builder
+ .setContentTitle(getString(R.string.notify_title))
+ .setContentText(String.format(getString(R.string.notify_msg), name))
+ .setPriority(Notification.PRIORITY_MIN)
+ .setSmallIcon(R.drawable.ic_vpn)
+ .setContentIntent(contentIntent)
+ .build());
+
+ // Create an fd.
+ configure(name, route, perApp, appBypass, appList, ipv6);
+
+ if (DEBUG)
+ Log.d(TAG, "fd: " + mInterface.getFd());
+
+ if (mInterface != null)
+ start(mInterface.getFd(), server, port, username, passwd, dns, dnsPort, ipv6, udpgw);
+
+ return START_STICKY;
+ }
+
+ @Override
+ public void onRevoke() {
+ super.onRevoke();
+ stopMe();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ stopMe();
+ }
+
+ private void stopMe() {
+ stopForeground(true);
+
+ Utility.killPidFile(getFilesDir() + "/tun2socks.pid");
+ Utility.killPidFile(getFilesDir() + "/pdnsd.pid");
+
+ try {
+ System.jniclose(mInterface.getFd());
+ mInterface.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ stopSelf();
+ }
+
+ private void configure(String name, String route, boolean perApp, boolean bypass, String[] apps, boolean ipv6) {
+ Builder b = new Builder();
+ b.setMtu(1500)
+ .setSession(name)
+ .addAddress("26.26.26.1", 24)
+ .addDnsServer("8.8.8.8");
+
+ if (ipv6) {
+ // Route all IPv6 traffic
+ b.addAddress("fdfe:dcba:9876::1", 126)
+ .addRoute("::", 0);
+ }
+
+ Routes.addRoutes(this, b, route);
+
+ // Add the default DNS
+ // Note that this DNS is just a stub.
+ // Actual DNS requests will be redirected through pdnsd.
+ b.addRoute("8.8.8.8", 32);
+
+ // Do app routing
+ if (!perApp) {
+ // Just bypass myself
+ try {
+ b.addDisallowedApplication("net.typeblog.socks");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ if (bypass) {
+ // First, bypass myself
+ try {
+ b.addDisallowedApplication("net.typeblog.socks");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ for (String p : apps) {
+ if (TextUtils.isEmpty(p))
+ continue;
+
+ try {
+ b.addDisallowedApplication(p.trim());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } else {
+ for (String p : apps) {
+ if (TextUtils.isEmpty(p) || p.trim().equals("net.typeblog.socks")) {
+ continue;
+ }
+
+ try {
+ b.addAllowedApplication(p.trim());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ mInterface = b.establish();
+ }
+
+ private void start(int fd, String server, int port, String user, String passwd, String dns, int dnsPort, boolean ipv6, String udpgw) {
+ // Start DNS daemon first
+ Utility.makePdnsdConf(this, dns, dnsPort);
+
+ Utility.exec(String.format(Locale.US, "%s/pdnsd -c %s/pdnsd.conf", getFilesDir(), getFilesDir()));
+
+ String command = String.format(Locale.US,
+ "%s/tun2socks --netif-ipaddr 26.26.26.2"
+ + " --netif-netmask 255.255.255.0"
+ + " --socks-server-addr %s:%d"
+ + " --tunfd %d"
+ + " --tunmtu 1500"
+ + " --loglevel 3"
+ + " --pid %s/tun2socks.pid"
+ , getFilesDir(), server, port, fd, getFilesDir());
+
+ if (user != null) {
+ command += " --username " + user;
+ command += " --password " + passwd;
+ }
+
+ if (ipv6) {
+ command += " --netif-ip6addr fdfe:dcba:9876::2";
+ }
+
+ command += " --dnsgw 26.26.26.1:8091";
+
+ if (udpgw != null) {
+ command += " --udpgw-remote-server-addr " + udpgw;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, command);
+ }
+
+ if (Utility.exec(command) != 0) {
+ stopMe();
+ return;
+ }
+
+ // Try to send the Fd through socket.
+ int i = 0;
+ while (i < 5) {
+ if (System.sendfd(fd) != -1) {
+ mRunning = true;
+ return;
+ }
+
+ i++;
+
+ try {
+ Thread.sleep(1000 * i);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Should not get here. Must be a failure.
+ stopMe();
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/System.java b/app/src/main/java/net/typeblog/socks/System.java
index 3edc553..24ea3e7 100644
--- a/app/src/main/java/net/typeblog/socks/System.java
+++ b/app/src/main/java/net/typeblog/socks/System.java
@@ -2,12 +2,12 @@
public class System
{
- static {
- java.lang.System.loadLibrary("system");
- }
+ static {
+ java.lang.System.loadLibrary("system");
+ }
- public static native void exec(String cmd);
- public static native String getABI();
- public static native int sendfd(int fd);
- public static native void jniclose(int fd);
+ public static native void exec(String cmd);
+ public static native String getABI();
+ public static native int sendfd(int fd);
+ public static native void jniclose(int fd);
}
diff --git a/app/src/main/java/net/typeblog/socks/util/Constants.java b/app/src/main/java/net/typeblog/socks/util/Constants.java
index fc87236..8bf3f4e 100644
--- a/app/src/main/java/net/typeblog/socks/util/Constants.java
+++ b/app/src/main/java/net/typeblog/socks/util/Constants.java
@@ -2,44 +2,40 @@
public class Constants
{
- public static final String DIR = "/data/data/net.typeblog.socks/files";
-
- public static final String ABI_DEFAULT = "armeabi-v7a";
-
- public static final String ROUTE_ALL = "all",
- ROUTE_CHN = "chn";
-
- public static final String INTENT_PREFIX = "SOCKS",
- INTENT_NAME = INTENT_PREFIX + "NAME",
- INTENT_SERVER = INTENT_PREFIX + "SERV",
- INTENT_PORT = INTENT_PREFIX + "PORT",
- INTENT_USERNAME = INTENT_PREFIX + "UNAME",
- INTENT_PASSWORD = INTENT_PREFIX + "PASSWD",
- INTENT_ROUTE = INTENT_PREFIX + "ROUTE",
- INTENT_DNS = INTENT_PREFIX + "DNS",
- INTENT_DNS_PORT = INTENT_PREFIX + "DNSPORT",
- INTENT_PER_APP = INTENT_PREFIX + "PERAPP",
- INTENT_APP_BYPASS = INTENT_PREFIX + "APPBYPASS",
- INTENT_APP_LIST = INTENT_PREFIX + "APPLIST",
- INTENT_IPV6_PROXY = INTENT_PREFIX + "IPV6",
- INTENT_UDP_GW = INTENT_PREFIX + "UDPGW";
-
- public static final String PREF = "profile",
- PREF_PROFILE = "profile",
- PREF_LAST_PROFILE = "last_profile",
- PREF_SERVER_IP = "server_ip",
- PREF_SERVER_PORT = "server_port",
- PREF_IPV6_PROXY = "ipv6_proxy",
- PREF_UDP_PROXY = "udp_proxy",
- PREF_UDP_GW = "udp_gw",
- PREF_AUTH_USERPW = "auth_userpw",
- PREF_AUTH_USERNAME = "auth_username",
- PREF_AUTH_PASSWORD = "auth_password",
- PREF_ADV_ROUTE = "adv_route",
- PREF_ADV_DNS = "adv_dns",
- PREF_ADV_DNS_PORT = "adv_dns_port",
- PREF_ADV_PER_APP = "adv_per_app",
- PREF_ADV_APP_BYPASS = "adv_app_bypass",
- PREF_ADV_APP_LIST = "adv_app_list",
- PREF_ADV_AUTO_CONNECT = "adv_auto_connect";
+ public static final String ROUTE_ALL = "all",
+ ROUTE_CHN = "chn";
+
+ private static final String INTENT_PREFIX = "SOCKS";
+ public static final String INTENT_NAME = INTENT_PREFIX + "NAME",
+ INTENT_SERVER = INTENT_PREFIX + "SERV",
+ INTENT_PORT = INTENT_PREFIX + "PORT",
+ INTENT_USERNAME = INTENT_PREFIX + "UNAME",
+ INTENT_PASSWORD = INTENT_PREFIX + "PASSWD",
+ INTENT_ROUTE = INTENT_PREFIX + "ROUTE",
+ INTENT_DNS = INTENT_PREFIX + "DNS",
+ INTENT_DNS_PORT = INTENT_PREFIX + "DNSPORT",
+ INTENT_PER_APP = INTENT_PREFIX + "PERAPP",
+ INTENT_APP_BYPASS = INTENT_PREFIX + "APPBYPASS",
+ INTENT_APP_LIST = INTENT_PREFIX + "APPLIST",
+ INTENT_IPV6_PROXY = INTENT_PREFIX + "IPV6",
+ INTENT_UDP_GW = INTENT_PREFIX + "UDPGW";
+
+ public static final String PREF = "profile",
+ PREF_PROFILE = "profile",
+ PREF_LAST_PROFILE = "last_profile",
+ PREF_SERVER_IP = "server_ip",
+ PREF_SERVER_PORT = "server_port",
+ PREF_IPV6_PROXY = "ipv6_proxy",
+ PREF_UDP_PROXY = "udp_proxy",
+ PREF_UDP_GW = "udp_gw",
+ PREF_AUTH_USERPW = "auth_userpw",
+ PREF_AUTH_USERNAME = "auth_username",
+ PREF_AUTH_PASSWORD = "auth_password",
+ PREF_ADV_ROUTE = "adv_route",
+ PREF_ADV_DNS = "adv_dns",
+ PREF_ADV_DNS_PORT = "adv_dns_port",
+ PREF_ADV_PER_APP = "adv_per_app",
+ PREF_ADV_APP_BYPASS = "adv_app_bypass",
+ PREF_ADV_APP_LIST = "adv_app_list",
+ PREF_ADV_AUTO_CONNECT = "adv_auto_connect";
}
diff --git a/app/src/main/java/net/typeblog/socks/util/Profile.java b/app/src/main/java/net/typeblog/socks/util/Profile.java
index 7a7c5f3..2f83e45 100644
--- a/app/src/main/java/net/typeblog/socks/util/Profile.java
+++ b/app/src/main/java/net/typeblog/socks/util/Profile.java
@@ -1,172 +1,169 @@
package net.typeblog.socks.util;
-import android.content.Context;
import android.content.SharedPreferences;
import static net.typeblog.socks.util.Constants.*;
public class Profile {
- private Context mContext;
- private SharedPreferences mPref;
- private String mName;
- private String mPrefix;
-
- Profile(Context context, SharedPreferences pref, String name) {
- mContext = context;
- mPref = pref;
- mName = name;
- mPrefix = prefPrefix(name);
- }
-
- public String getName() {
- return mName;
- }
-
- public String getServer() {
- return mPref.getString(key("server"), "127.0.0.1");
- }
-
- public void setServer(String server) {
- mPref.edit().putString(key("server"), server).commit();
- }
-
- public int getPort() {
- return mPref.getInt(key("port"), 1080);
- }
-
- public void setPort(int port) {
- mPref.edit().putInt(key("port"), port).commit();
- }
-
- public boolean isUserPw() {
- return mPref.getBoolean(key("userpw"), false);
- }
-
- public void setIsUserpw(boolean is) {
- mPref.edit().putBoolean(key("userpw"), is).commit();
- }
-
- public String getUsername() {
- return mPref.getString(key("username"), "");
- }
-
- public void setUsername(String username) {
- mPref.edit().putString(key("username"), username).commit();
- }
-
- public String getPassword() {
- return mPref.getString(key("password"), "");
- }
-
- public void setPassword(String password) {
- mPref.edit().putString(key("password"), password).commit();
- }
-
- public String getRoute() {
- return mPref.getString(key("route"), ROUTE_ALL);
- }
-
- public void setRoute(String route) {
- mPref.edit().putString(key("route"), route).commit();
- }
-
- public String getDns() {
- return mPref.getString(key("dns"), "8.8.8.8");
- }
-
- public void setDns(String dns) {
- mPref.edit().putString(key("dns"), dns).commit();
- }
-
- public int getDnsPort() {
- return mPref.getInt(key("dns_port"), 53);
- }
-
- public void setDnsPort(int port) {
- mPref.edit().putInt(key("dns_port"), port).commit();
- }
-
- public boolean isPerApp() {
- return mPref.getBoolean(key("perapp"), false);
- }
-
- public void setIsPerApp(boolean is) {
- mPref.edit().putBoolean(key("perapp"), is).commit();
- }
-
- public boolean isBypassApp() {
- return mPref.getBoolean(key("appbypass"), false);
- }
-
- public void setIsBypassApp(boolean is) {
- mPref.edit().putBoolean(key("appbypass"), is).commit();
- }
-
- public String getAppList() {
- return mPref.getString(key("applist"), "");
- }
-
- public void setAppList(String list) {
- mPref.edit().putString(key("applist"), list).commit();
- }
-
- public boolean hasIPv6() {
- return mPref.getBoolean(key("ipv6"), false);
- }
-
- public void setHasIPv6(boolean has) {
- mPref.edit().putBoolean(key("ipv6"), has).commit();
- }
-
- public boolean hasUDP() {
- return mPref.getBoolean(key("udp"), false);
- }
-
- public void setHasUDP(boolean has) {
- mPref.edit().putBoolean(key("udp"), has).commit();
- }
-
- public String getUDPGW() {
- return mPref.getString(key("udpgw"), "127.0.0.1:7300");
- }
-
- public void setUDPGW(String gw) {
- mPref.edit().putString(key("udpgw"), gw).commit();
- }
-
- public boolean autoConnect() {
- return mPref.getBoolean(key("auto"), false);
- }
-
- public void setAutoConnect(boolean auto) {
- mPref.edit().putBoolean(key("auto"), auto).commit();
- }
-
- void delete() {
- mPref.edit()
- .remove(key("server"))
- .remove(key("port"))
- .remove(key("userpw"))
- .remove(key("username"))
- .remove(key("password"))
- .remove(key("route"))
- .remove(key("dns"))
- .remove(key("dns_port"))
- .remove(key("perapp"))
- .remove(key("appbypass"))
- .remove(key("applist"))
- .remove(key("ipv6"))
- .remove(key("udp"))
- .remove(key("udpgw"))
- .remove(key("auto"))
- .commit();
- }
-
- private String key(String k) {
- return mPrefix + k;
- }
-
- private static String prefPrefix(String name) {
- return name.replace("_", "__").replace(" ", "_");
- }
+ private final SharedPreferences mPref;
+ private final String mName;
+ private final String mPrefix;
+
+ Profile(SharedPreferences pref, String name) {
+ mPref = pref;
+ mName = name;
+ mPrefix = prefPrefix(name);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getServer() {
+ return mPref.getString(key("server"), "127.0.0.1");
+ }
+
+ public void setServer(String server) {
+ mPref.edit().putString(key("server"), server).apply();
+ }
+
+ public int getPort() {
+ return mPref.getInt(key("port"), 1080);
+ }
+
+ public void setPort(int port) {
+ mPref.edit().putInt(key("port"), port).apply();
+ }
+
+ public boolean isUserPw() {
+ return mPref.getBoolean(key("userpw"), false);
+ }
+
+ public void setIsUserpw(boolean is) {
+ mPref.edit().putBoolean(key("userpw"), is).apply();
+ }
+
+ public String getUsername() {
+ return mPref.getString(key("username"), "");
+ }
+
+ public void setUsername(String username) {
+ mPref.edit().putString(key("username"), username).apply();
+ }
+
+ public String getPassword() {
+ return mPref.getString(key("password"), "");
+ }
+
+ public void setPassword(String password) {
+ mPref.edit().putString(key("password"), password).apply();
+ }
+
+ public String getRoute() {
+ return mPref.getString(key("route"), ROUTE_ALL);
+ }
+
+ public void setRoute(String route) {
+ mPref.edit().putString(key("route"), route).apply();
+ }
+
+ public String getDns() {
+ return mPref.getString(key("dns"), "8.8.8.8");
+ }
+
+ public void setDns(String dns) {
+ mPref.edit().putString(key("dns"), dns).apply();
+ }
+
+ public int getDnsPort() {
+ return mPref.getInt(key("dns_port"), 53);
+ }
+
+ public void setDnsPort(int port) {
+ mPref.edit().putInt(key("dns_port"), port).apply();
+ }
+
+ public boolean isPerApp() {
+ return mPref.getBoolean(key("perapp"), false);
+ }
+
+ public void setIsPerApp(boolean is) {
+ mPref.edit().putBoolean(key("perapp"), is).apply();
+ }
+
+ public boolean isBypassApp() {
+ return mPref.getBoolean(key("appbypass"), false);
+ }
+
+ public void setIsBypassApp(boolean is) {
+ mPref.edit().putBoolean(key("appbypass"), is).apply();
+ }
+
+ public String getAppList() {
+ return mPref.getString(key("applist"), "");
+ }
+
+ public void setAppList(String list) {
+ mPref.edit().putString(key("applist"), list).apply();
+ }
+
+ public boolean hasIPv6() {
+ return mPref.getBoolean(key("ipv6"), false);
+ }
+
+ public void setHasIPv6(boolean has) {
+ mPref.edit().putBoolean(key("ipv6"), has).apply();
+ }
+
+ public boolean hasUDP() {
+ return mPref.getBoolean(key("udp"), false);
+ }
+
+ public void setHasUDP(boolean has) {
+ mPref.edit().putBoolean(key("udp"), has).apply();
+ }
+
+ public String getUDPGW() {
+ return mPref.getString(key("udpgw"), "127.0.0.1:7300");
+ }
+
+ public void setUDPGW(String gw) {
+ mPref.edit().putString(key("udpgw"), gw).apply();
+ }
+
+ public boolean autoConnect() {
+ return mPref.getBoolean(key("auto"), false);
+ }
+
+ public void setAutoConnect(boolean auto) {
+ mPref.edit().putBoolean(key("auto"), auto).apply();
+ }
+
+ void delete() {
+ mPref.edit()
+ .remove(key("server"))
+ .remove(key("port"))
+ .remove(key("userpw"))
+ .remove(key("username"))
+ .remove(key("password"))
+ .remove(key("route"))
+ .remove(key("dns"))
+ .remove(key("dns_port"))
+ .remove(key("perapp"))
+ .remove(key("appbypass"))
+ .remove(key("applist"))
+ .remove(key("ipv6"))
+ .remove(key("udp"))
+ .remove(key("udpgw"))
+ .remove(key("auto"))
+ .apply();
+ }
+
+ private String key(String k) {
+ return mPrefix + k;
+ }
+
+ private static String prefPrefix(String name) {
+ return name.replace("_", "__").replace(" ", "_");
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/util/ProfileFactory.java b/app/src/main/java/net/typeblog/socks/util/ProfileFactory.java
deleted file mode 100644
index f3ab4fd..0000000
--- a/app/src/main/java/net/typeblog/socks/util/ProfileFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package net.typeblog.socks.util;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.lang.ref.WeakReference;
-
-class ProfileFactory
-{
- private static ProfileFactory sInstance;
-
- public static final ProfileFactory getInstance(Context context, SharedPreferences pref) {
- if (sInstance == null) {
- sInstance = new ProfileFactory(context, pref);
- }
-
- return sInstance;
- }
-
- private Context mContext;
- private SharedPreferences mPref;
- private Map> mMap = new HashMap<>();
-
- private ProfileFactory(Context context, SharedPreferences pref) {
- mContext = context;
- mPref = pref;
- }
-
- public Profile getProfile(String name) {
- WeakReference p = mMap.get(name);
-
- if (p == null || p.get() == null) {
- p = new WeakReference(new Profile(mContext, mPref, name));
- mMap.put(name, p);
- }
-
- return p.get();
- }
-}
diff --git a/app/src/main/java/net/typeblog/socks/util/ProfileManager.java b/app/src/main/java/net/typeblog/socks/util/ProfileManager.java
index 95cbeae..9b5345a 100644
--- a/app/src/main/java/net/typeblog/socks/util/ProfileManager.java
+++ b/app/src/main/java/net/typeblog/socks/util/ProfileManager.java
@@ -11,89 +11,79 @@
import static net.typeblog.socks.util.Constants.*;
public class ProfileManager {
- private static ProfileManager sInstance;
-
- public static final ProfileManager getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new ProfileManager(context);
- }
-
- return sInstance;
- }
-
- private SharedPreferences mPref;
- private Context mContext;
- private ProfileFactory mFactory;
- private List mProfiles = new ArrayList<>();
-
- private ProfileManager(Context context) {
- mContext = context;
- mPref = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE);
- mFactory = ProfileFactory.getInstance(mContext, mPref);
- reload();
- }
-
- public void reload() {
- mProfiles.clear();
- mProfiles.add(mContext.getString(R.string.prof_default));
-
- String[] profiles = mPref.getString(PREF_PROFILE, "").split("\n");
-
- for (String p : profiles) {
- if (!TextUtils.isEmpty(p)) {
- mProfiles.add(p);
- }
- }
- }
-
- public String[] getProfiles() {
- return mProfiles.toArray(new String[mProfiles.size()]);
- }
-
- public Profile getProfile(String name) {
- if (!mProfiles.contains(name)) {
- return null;
- } else {
- return mFactory.getProfile(name);
- }
- }
-
- public Profile getDefault() {
- return getProfile(mPref.getString(PREF_LAST_PROFILE, mProfiles.get(0)));
- }
-
- public void switchDefault(String name) {
- if (mProfiles.contains(name))
- mPref.edit().putString(PREF_LAST_PROFILE, name).commit();
- }
-
- public Profile addProfile(String name) {
- if (mProfiles.contains(name)) {
- return null;
- } else {
- mProfiles.add(name);
- mProfiles.remove(0);
- mPref.edit().putString(PREF_PROFILE, Utility.join(mProfiles, "\n"))
- .putString(PREF_LAST_PROFILE, name).commit();
- reload();
- return getDefault();
- }
- }
-
- public boolean removeProfile(String name) {
- if (name == mProfiles.get(0) || !mProfiles.contains(name)) {
- return false;
- }
-
- getProfile(name).delete();
-
- mProfiles.remove(0);
- mProfiles.remove(name);
-
- mPref.edit().putString(PREF_PROFILE, Utility.join(mProfiles, "\n"))
- .remove(PREF_LAST_PROFILE).commit();
- reload();
-
- return true;
- }
+
+ private final SharedPreferences mPref;
+ private final Context mContext;
+
+ public ProfileManager(Context context) {
+ mContext = context;
+ mPref = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE);
+ }
+
+ private List getProfileList() {
+ List mProfiles = new ArrayList<>();
+ mProfiles.clear();
+ mProfiles.add(mContext.getString(R.string.prof_default));
+
+ //noinspection ConstantConditions
+ String[] profiles = mPref.getString(PREF_PROFILE, "").split("\n");
+
+ for (String p : profiles) {
+ if (!TextUtils.isEmpty(p)) {
+ mProfiles.add(p);
+ }
+ }
+ return mProfiles;
+ }
+
+ public String[] getProfiles() {
+ return getProfileList().toArray(new String[0]);
+ }
+
+ public Profile getProfile(String name) {
+ if (!getProfileList().contains(name)) {
+ return null;
+ } else {
+ return new Profile(mPref, name);
+ }
+ }
+
+ public Profile getDefault() {
+ return new Profile(mPref, mPref.getString(PREF_LAST_PROFILE, getProfileList().get(0)));
+ }
+
+ public void switchDefault(String name) {
+ if (getProfileList().contains(name))
+ mPref.edit().putString(PREF_LAST_PROFILE, name).apply();
+ }
+
+ public Profile addProfile(String name) {
+ List mProfiles = getProfileList();
+ if (mProfiles.contains(name)) {
+ return null;
+ } else {
+ mProfiles.add(name);
+ mProfiles.remove(0);
+ mPref.edit().putString(PREF_PROFILE, Utility.join(mProfiles, "\n"))
+ .putString(PREF_LAST_PROFILE, name).apply();
+ return getDefault();
+ }
+ }
+
+ public boolean removeProfile(String name) {
+ List mProfiles = getProfileList();
+ if (name.equals(mProfiles.get(0)) || !mProfiles.contains(name)) {
+ return false;
+ }
+
+ new Profile(mPref, name).delete();
+
+ mProfiles.remove(0);
+ mProfiles.remove(name);
+
+ mPref.edit().putString(PREF_PROFILE, Utility.join(mProfiles, "\n"))
+ .remove(PREF_LAST_PROFILE).apply();
+
+ return true;
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/util/Routes.java b/app/src/main/java/net/typeblog/socks/util/Routes.java
index e2fc264..18a5fe2 100644
--- a/app/src/main/java/net/typeblog/socks/util/Routes.java
+++ b/app/src/main/java/net/typeblog/socks/util/Routes.java
@@ -7,27 +7,21 @@
import static net.typeblog.socks.util.Constants.*;
public class Routes {
- public static void addRoutes(Context context, VpnService.Builder builder, String name) {
- String[] routes = null;
- switch (name) {
- case ROUTE_ALL:
- routes = new String[]{"0.0.0.0/0"};
- break;
- case ROUTE_CHN:
- routes = context.getResources().getStringArray(R.array.simple_route);
- break;
- default:
- routes = new String[]{"0.0.0.0/0"};
- break;
- }
-
- for (String r : routes) {
- String[] cidr = r.split("/");
-
- // Cannot handle 127.0.0.0/8
- if (cidr.length == 2 && !cidr[0].startsWith("127")) {
- builder.addRoute(cidr[0], Integer.parseInt(cidr[1]));
- }
- }
- }
+ public static void addRoutes(Context context, VpnService.Builder builder, String name) {
+ String[] routes;
+ if(ROUTE_CHN.equals(name)) {
+ routes = context.getResources().getStringArray(R.array.simple_route);
+ } else {
+ routes = new String[]{"0.0.0.0/0"};
+ }
+
+ for (String r : routes) {
+ String[] cidr = r.split("/");
+
+ // Cannot handle 127.0.0.0/8
+ if (cidr.length == 2 && !cidr[0].startsWith("127")) {
+ builder.addRoute(cidr[0], Integer.parseInt(cidr[1]));
+ }
+ }
+ }
}
diff --git a/app/src/main/java/net/typeblog/socks/util/Utility.java b/app/src/main/java/net/typeblog/socks/util/Utility.java
index b101805..30065d1 100644
--- a/app/src/main/java/net/typeblog/socks/util/Utility.java
+++ b/app/src/main/java/net/typeblog/socks/util/Utility.java
@@ -21,197 +21,203 @@
import static net.typeblog.socks.util.Constants.*;
public class Utility {
- private static final String TAG = Utility.class.getSimpleName();
-
- public static void extractFile(Context context) {
- // Check app version
- SharedPreferences pref = context.getSharedPreferences("ver", Context.MODE_PRIVATE);
-
- int ver = 0;
- try {
- ver = context.getPackageManager().getPackageInfo("net.typeblog.socks", 0).versionCode;
- } catch (Exception e) {
- throw new RuntimeException(e);
- //return;
- }
-
- if (pref.getInt("ver", -1) == ver) {
- return;
- }
-
- String target = DIR;
-
- if (DEBUG) {
- Log.d(TAG, "target = " + target);
- }
-
- if (new File(target + "/tun2socks").exists()) {
- new File(target + "/tun2socks").delete();
- }
-
- if (new File(target + "/pdnsd").exists()) {
- new File(target + "/pdnsd").delete();
- }
-
- new File(target).mkdir();
-
- String source = System.getABI();
-
- AssetManager m = context.getAssets();
-
- String[] files = null;
- try {
- files = m.list(source);
- } catch (IOException e) {
-
- }
-
- if (files == null || files.length == 0) {
- return;
- }
-
- for (String f : files) {
- InputStream in = null;
- OutputStream out = null;
-
- try {
- in = m.open(source + "/" + f);
- out = new FileOutputStream(target + "/" + f);
-
- byte[] buf = new byte[512];
- int len;
-
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
-
- in.close();
- out.flush();
- out.close();
-
- exec(String.format("chmod 755 %s/%s", target, f));
- } catch (Exception e) {
-
- }
- }
-
- pref.edit().putInt("ver", ver).commit();
-
- }
-
- public static int exec(String cmd) {
- try {
- Process p = Runtime.getRuntime().exec(cmd);
-
- int ret = p.waitFor();
-
-
- return ret;
- } catch (Exception e) {
- return -1;
- }
- }
-
- public static void killPidFile(String f) {
- File file = new File(f);
-
- if (!file.exists()) {
- return;
- }
-
- InputStream i = null;
- try {
- i = new FileInputStream(file);
- } catch (Exception e) {
- return;
- }
-
- byte[] buf = new byte[512];
- int len;
- StringBuilder str = new StringBuilder();
-
- try {
- while ((len = i.read(buf, 0, 512)) > 0) {
- str.append(new String(buf, 0, len));
- }
- i.close();
- } catch (Exception e) {
- return;
- }
-
- try {
- int pid = Integer.parseInt(str.toString().trim().replace("\n", ""));
- Runtime.getRuntime().exec("kill " + pid).waitFor();
- file.delete();
- } catch (Exception e) {
-
- }
- }
-
- public static String join(List list, String separator) {
- StringBuilder ret = new StringBuilder();
-
- for (String s : list) {
- ret.append(s).append(separator);
- }
-
- return ret.substring(0, ret.length() - separator.length());
- }
-
- public static void makePdnsdConf(Context context, String dns, int port) {
- String conf = String.format(context.getString(R.string.pdnsd_conf), dns, port);
-
- File f = new File(DIR + "/pdnsd.conf");
-
- if (f.exists()) {
- f.delete();
- }
-
- try {
- OutputStream out = new FileOutputStream(f);
- out.write(conf.getBytes());
- out.flush();
- out.close();
- } catch (Exception e) {
-
- }
-
- File cache = new File(DIR + "/pdnsd.cache");
-
- if (!cache.exists()) {
- try {
- cache.createNewFile();
- } catch (Exception e) {
-
- }
- }
- }
-
- public static void startVpn(Context context, Profile profile) {
- Intent i = new Intent(context, SocksVpnService.class)
- .putExtra(INTENT_NAME, profile.getName())
- .putExtra(INTENT_SERVER, profile.getServer())
- .putExtra(INTENT_PORT, profile.getPort())
- .putExtra(INTENT_ROUTE, profile.getRoute())
- .putExtra(INTENT_DNS, profile.getDns())
- .putExtra(INTENT_DNS_PORT, profile.getDnsPort())
- .putExtra(INTENT_PER_APP, profile.isPerApp())
- .putExtra(INTENT_IPV6_PROXY, profile.hasIPv6());
-
- if (profile.isUserPw()) {
- i.putExtra(INTENT_USERNAME, profile.getUsername())
- .putExtra(INTENT_PASSWORD, profile.getPassword());
- }
-
- if (profile.isPerApp()) {
- i.putExtra(INTENT_APP_BYPASS, profile.isBypassApp())
- .putExtra(INTENT_APP_LIST, profile.getAppList().split("\n"));
- }
-
- if (profile.hasUDP()) {
- i.putExtra(INTENT_UDP_GW, profile.getUDPGW());
- }
-
- context.startService(i);
- }
+ private static final String TAG = Utility.class.getSimpleName();
+
+ public static void extractFile(Context context) {
+ // Check app version
+ SharedPreferences pref = context.getSharedPreferences("ver", Context.MODE_PRIVATE);
+
+ int ver;
+ try {
+ ver = context.getPackageManager().getPackageInfo("net.typeblog.socks", 0).versionCode;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ //return;
+ }
+
+ if (pref.getInt("ver", -1) == ver) {
+ return;
+ }
+
+ String target = context.getFilesDir().toString();
+
+ if (DEBUG) {
+ Log.d(TAG, "target = " + target);
+ }
+
+ if (new File(target + "/tun2socks").exists()) {
+ if(!new File(target + "/tun2socks").delete())
+ Log.w(TAG, "failed to delete tun2socks");
+ }
+
+ if (new File(target + "/pdnsd").exists()) {
+ if(!new File(target + "/pdnsd").delete())
+ Log.w(TAG, "failed to delete pdnsd");
+ }
+
+ if(!new File(target).mkdir())
+ Log.w(TAG, "failed to create directory");
+
+ String source = System.getABI();
+
+ AssetManager m = context.getAssets();
+
+ String[] files = null;
+ try {
+ files = m.list(source);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (files == null || files.length == 0) {
+ return;
+ }
+
+ for (String f : files) {
+ InputStream in;
+ OutputStream out;
+
+ try {
+ in = m.open(source + "/" + f);
+ out = new FileOutputStream(target + "/" + f);
+
+ byte[] buf = new byte[512];
+ int len;
+
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+
+ in.close();
+ out.flush();
+ out.close();
+
+ exec(String.format("chmod 755 %s/%s", target, f));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ pref.edit().putInt("ver", ver).apply();
+
+ }
+
+ public static int exec(String cmd) {
+ try {
+ Process p = Runtime.getRuntime().exec(cmd);
+
+ return p.waitFor();
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static void killPidFile(String f) {
+ File file = new File(f);
+
+ if (!file.exists()) {
+ return;
+ }
+
+ InputStream i;
+ try {
+ i = new FileInputStream(file);
+ } catch (Exception e) {
+ return;
+ }
+
+ byte[] buf = new byte[512];
+ int len;
+ StringBuilder str = new StringBuilder();
+
+ try {
+ while ((len = i.read(buf, 0, 512)) > 0) {
+ str.append(new String(buf, 0, len));
+ }
+ i.close();
+ } catch (Exception e) {
+ return;
+ }
+
+ try {
+ int pid = Integer.parseInt(str.toString().trim().replace("\n", ""));
+ Runtime.getRuntime().exec("kill " + pid).waitFor();
+ if(!file.delete())
+ Log.w(TAG, "failed to delete pidfile");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String join(List list, String separator) {
+ if (list.isEmpty()) return "";
+
+ StringBuilder ret = new StringBuilder();
+
+ for (String s : list) {
+ ret.append(s).append(separator);
+ }
+
+ return ret.substring(0, ret.length() - separator.length());
+ }
+
+ public static void makePdnsdConf(Context context, String dns, int port) {
+ String conf = context.getString(R.string.pdnsd_conf)
+ .replace("{IP}", dns).replace("{PORT}", Integer.toString(port));
+
+ File f = new File(context.getFilesDir() + "/pdnsd.conf");
+
+ if (f.exists()) {
+ if(!f.delete())
+ Log.w(TAG, "failed to delete pdnsd.conf");
+ }
+
+ try {
+ OutputStream out = new FileOutputStream(f);
+ out.write(conf.getBytes());
+ out.flush();
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ File cache = new File(context.getFilesDir() + "/pdnsd.cache");
+
+ if (!cache.exists()) {
+ try {
+ if(!cache.createNewFile())
+ Log.w(TAG, "failed to create pdnsd.cache");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void startVpn(Context context, Profile profile) {
+ Intent i = new Intent(context, SocksVpnService.class)
+ .putExtra(INTENT_NAME, profile.getName())
+ .putExtra(INTENT_SERVER, profile.getServer())
+ .putExtra(INTENT_PORT, profile.getPort())
+ .putExtra(INTENT_ROUTE, profile.getRoute())
+ .putExtra(INTENT_DNS, profile.getDns())
+ .putExtra(INTENT_DNS_PORT, profile.getDnsPort())
+ .putExtra(INTENT_PER_APP, profile.isPerApp())
+ .putExtra(INTENT_IPV6_PROXY, profile.hasIPv6());
+
+ if (profile.isUserPw()) {
+ i.putExtra(INTENT_USERNAME, profile.getUsername())
+ .putExtra(INTENT_PASSWORD, profile.getPassword());
+ }
+
+ if (profile.isPerApp()) {
+ i.putExtra(INTENT_APP_BYPASS, profile.isBypassApp())
+ .putExtra(INTENT_APP_LIST, profile.getAppList().split("\n"));
+ }
+
+ if (profile.hasUDP()) {
+ i.putExtra(INTENT_UDP_GW, profile.getUDPGW());
+ }
+
+ context.startService(i);
+ }
}
diff --git a/app/src/main/jni/Application.mk b/app/src/main/jni/Application.mk
index 3ac89c0..cc6dcde 100644
--- a/app/src/main/jni/Application.mk
+++ b/app/src/main/jni/Application.mk
@@ -1,4 +1,3 @@
-APP_ABI := armeabi-v7a arm64-v8a mips x86
+APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-21
-APP_STL := stlport_static
-NDK_TOOLCHAIN_VERSION := 4.9
+APP_STL := c++_static
diff --git a/app/src/main/jni/system.cpp b/app/src/main/jni/system.cpp
index df657d2..ad71a2a 100644
--- a/app/src/main/jni/system.cpp
+++ b/app/src/main/jni/system.cpp
@@ -25,14 +25,10 @@ jstring Java_net_typeblog_socks_system_getabi(JNIEnv *env, jobject thiz) {
if (family == ANDROID_CPU_FAMILY_X86) {
abi = "x86";
- } else if (family == ANDROID_CPU_FAMILY_MIPS) {
- abi = "mips";
+ } else if (family == ANDROID_CPU_FAMILY_X86_64) {
+ abi = "x86_64";
} else if (family == ANDROID_CPU_FAMILY_ARM) {
- // if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
abi = "armeabi-v7a";
- // } else {
- // abi = "armeabi";
- // }
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
abi = "arm64-v8a";
}
diff --git a/app/src/main/jniLibs/arm64-v8a/libsystem.so b/app/src/main/jniLibs/arm64-v8a/libsystem.so
new file mode 100755
index 0000000..9324d47
Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libsystem.so differ
diff --git a/app/src/main/jniLibs/armeabi-v7a/libsystem.so b/app/src/main/jniLibs/armeabi-v7a/libsystem.so
new file mode 100755
index 0000000..9972e85
Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libsystem.so differ
diff --git a/app/src/main/jniLibs/x86/libsystem.so b/app/src/main/jniLibs/x86/libsystem.so
new file mode 100755
index 0000000..5bd7647
Binary files /dev/null and b/app/src/main/jniLibs/x86/libsystem.so differ
diff --git a/app/src/main/jniLibs/x86_64/libsystem.so b/app/src/main/jniLibs/x86_64/libsystem.so
new file mode 100755
index 0000000..dcc3437
Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libsystem.so differ
diff --git a/app/src/main/res/drawable-anydpi-v24/ic_vpn.xml b/app/src/main/res/drawable-anydpi-v24/ic_vpn.xml
new file mode 100644
index 0000000..913f164
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi-v24/ic_vpn.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_vpn.png b/app/src/main/res/drawable-hdpi/ic_vpn.png
new file mode 100644
index 0000000..c7fb217
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_vpn.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_vpn.png b/app/src/main/res/drawable-mdpi/ic_vpn.png
new file mode 100644
index 0000000..1528a1a
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_vpn.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_vpn.png b/app/src/main/res/drawable-xhdpi/ic_vpn.png
new file mode 100644
index 0000000..1978ca6
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_vpn.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72c..0000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_vpn.png b/app/src/main/res/drawable-xxhdpi/ic_vpn.png
new file mode 100644
index 0000000..8122c8e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_vpn.png differ
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..e3721a6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/action_switch.xml b/app/src/main/res/layout/action_switch.xml
index ec25fca..8ccb937 100644
--- a/app/src/main/res/layout/action_switch.xml
+++ b/app/src/main/res/layout/action_switch.xml
@@ -1,13 +1,13 @@
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
-
+
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
deleted file mode 100644
index 79b6b6d..0000000
--- a/app/src/main/res/layout/main.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 44dcea8..ea75ae9 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -1,17 +1,17 @@
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2f5e8ab
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..795f5cc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c85befa
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9b1b6eb
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..30bdfb1
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..5a0718b
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..62c11a4
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..cdbf7e6
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4fa9bd4
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bdee0b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index f3ba19b..84b5665 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1,12 +1,12 @@
-
- - @string/adv_route_all
- - @string/adv_route_non_chn
-
-
-
- - all
- - chn
-
+
+ - @string/adv_route_all
+ - @string/adv_route_non_chn
+
+
+
+ - all
+ - chn
+
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c1f65ba
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #5C6BC0
+
\ No newline at end of file
diff --git a/app/src/main/res/values/pdnsd.xml b/app/src/main/res/values/pdnsd.xml
index b2d69d8..3cd3217 100644
--- a/app/src/main/res/values/pdnsd.xml
+++ b/app/src/main/res/values/pdnsd.xml
@@ -16,8 +16,8 @@ global {
server {
label= "upstream";
- ip = %s;
- port = %d;
+ ip = {IP};
+ port = {PORT};
uptest = none;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8439e60..a91d490 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,45 +2,46 @@
SocksDroid
-
- Profile
- Default
-
- Connection
- Server IP
- Server Port
- IPv6
- Enable IPv6 forwarding. If the server supports IPv6, you can access IPv6 contents from IPv4 network with this enabled.
- UDP Forwarding
- Forward UDP packets to badvpn-udpgw via SOCKS5. This needs badvpn-udpgw running on the remote server (normally listening on 127.0.0.1) to work.
- UDP Gateway (Remote)
-
- Authentication
- Username & Password Authentication
- Username
- Password
-
- Advanced
- Route
- All (Default)
- Non-Chinese IPs
- DNS Server
- DNS Port (TCP)
- Connect on Boot
- Per-app Proxy
- Bypass Mode
- Enable this option to bypass selected apps instead of proxying them
- App List
- Enter one app\'s package name in one line. See Settings -> Apps for package names of apps.
-
- Add Profile
- Delete Profile
- Delete profile %s?
- Switch on / off the proxy
-
- Failed to add profile %s
- Error deleting profile %s
- SOCKS5 proxy activated
- Connected to 「%s」
-
+
+ Profile
+ Default
+
+ Connection
+ Server IP
+ Server Port
+ IPv6
+ Enable IPv6 forwarding. If the server supports IPv6, you can access IPv6 contents from IPv4 network with this enabled.
+ UDP Forwarding
+ Forward UDP packets to badvpn-udpgw via SOCKS5. This needs badvpn-udpgw running on the remote server (normally listening on 127.0.0.1) to work.
+ UDP Gateway (Remote)
+
+ Authentication
+ Username & Password Authentication
+ Username
+ Password
+
+ Advanced
+ Route
+ All (Default)
+ Non-Chinese IPs
+ DNS Server
+ DNS Port (TCP)
+ Connect on Boot
+ Per-app Proxy
+ Bypass Mode
+ Enable this option to bypass selected apps instead of proxying them
+ App List
+ Enter one app\'s package name in one line. See Settings -> Apps for package names of apps.
+
+ Add Profile
+ Delete Profile
+ Delete profile %s?
+ Switch on / off the proxy
+
+ Failed to add profile %s
+ Error deleting profile %s
+ SOCKS5 proxy activated
+ Connected to %s
+ Proxy active
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 8900c56..9b47735 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,8 +1,8 @@
+ - #5C6BC0
+ - #3949AB
+ - #FFC107
+
diff --git a/app/src/main/res/xml/backup_descriptor.xml b/app/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 0000000..a608293
--- /dev/null
+++ b/app/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index 4ed108e..2e82523 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -1,105 +1,105 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 3e7c358..df8f9e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,10 +2,11 @@
buildscript {
repositories {
+ google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.+'
+ classpath 'com.android.tools.build:gradle:3.5.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -14,6 +15,7 @@ buildscript {
allprojects {
repositories {
+ google()
jcenter()
}
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9a2ce44
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Oct 11 01:26:40 CEST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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 () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+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
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+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
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega