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