From ae3bcd740536ce783c3640f1c5ce1a13d699d8b8 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 14 Sep 2017 13:01:21 +0800 Subject: [PATCH 01/79] Update faq.md --- .github/faq.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/faq.md b/.github/faq.md index b061000c86..6a6eee829c 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -49,6 +49,8 @@ As Shadowsocks takes over the whole device network, any battery used by network So if you notice a significant increase in battery usage after you use Shadowsocks, it's most likely caused by other apps. For example, Google Play services can consume more battery after being able to connecting to Google, etc. +More details: https://kb.adguard.com/en/android/solving-problems/battery + ### It works fine under Wi-Fi but can't connect through cellular data? Allow this app to consume background data in app settings. From 9c1a640baf49a2ef48876918cc705e29cb30cd05 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 14 Sep 2017 18:10:57 -0700 Subject: [PATCH 02/79] Refine #1379 --- mobile/src/main/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 548fe90f8c..2008c83f21 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -339,7 +339,7 @@ include $(CLEAR_VARS) LOCAL_CFLAGS := -std=gnu99 LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE -LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL +LOCAL_CFLAGS += -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE LOCAL_CFLAGS += -DNDEBUG -DANDROID # LOCAL_CFLAGS += -DTUN2SOCKS_JNI From c00731326840cdc4e1c9a538cf73688104014a93 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 14 Sep 2017 19:21:43 -0700 Subject: [PATCH 03/79] Update shadowsocks-libev --- .gitmodules | 3 --- mobile/src/main/jni/Android.mk | 29 ++++----------------------- mobile/src/main/jni/libudns | 1 - mobile/src/main/jni/shadowsocks-libev | 2 +- 4 files changed, 5 insertions(+), 30 deletions(-) delete mode 160000 mobile/src/main/jni/libudns diff --git a/.gitmodules b/.gitmodules index 092ed998fa..09136bb10a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,9 +30,6 @@ [submodule "mobile/src/main/jni/libev"] path = mobile/src/main/jni/libev url = https://github.com/shadowsocks/libev.git -[submodule "mobile/src/main/jni/libudns"] - path = mobile/src/main/jni/libudns - url = https://github.com/shadowsocks/libudns.git [submodule "mobile/src/overture/go"] path = mobile/src/overture/go url = https://github.com/shadowsocks/go.git diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 2008c83f21..ccc3b4738b 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -186,26 +186,7 @@ LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libcork/src/libcork/,$(CORK_SOU include $(BUILD_STATIC_LIBRARY) ######################################################## -## libudns -######################################################## - -include $(CLEAR_VARS) - -UDNS_SOURCES := udns_dn.c udns_dntosp.c udns_parse.c udns_resolver.c udns_init.c \ - udns_misc.c udns_XtoX.c \ - udns_rr_a.c udns_rr_ptr.c udns_rr_mx.c udns_rr_txt.c udns_bl.c \ - udns_rr_srv.c udns_rr_naptr.c udns_codes.c udns_jran.c - -LOCAL_MODULE := libudns -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libudns \ - -DHAVE_DECL_INET_NTOP - -LOCAL_SRC_FILES := $(addprefix libudns/,$(UDNS_SOURCES)) - -include $(BUILD_STATIC_LIBRARY) - -######################################################## -## libev +## libev ######################################################## include $(CLEAR_VARS) @@ -215,7 +196,7 @@ LOCAL_CFLAGS += -O2 -DNDEBUG -DHAVE_CONFIG_H \ -I$(LOCAL_PATH)/include/libev LOCAL_SRC_FILES := \ libev/ev.c \ - libev/event.c + libev/event.c include $(BUILD_STATIC_LIBRARY) @@ -264,7 +245,6 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \ -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/mbedtls/include \ -I$(LOCAL_PATH)/pcre \ - -I$(LOCAL_PATH)/libudns \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ @@ -272,7 +252,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \ -I$(LOCAL_PATH)/shadowsocks-libev/libbloom \ -I$(LOCAL_PATH)/libev -LOCAL_STATIC_LIBRARIES := libev libmbedtls libipset libcork libbloom libudns \ +LOCAL_STATIC_LIBRARIES := libev libmbedtls libipset libcork libbloom \ libsodium libancillary libpcre LOCAL_LDLIBS := -llog @@ -298,7 +278,6 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ -DCONNECT_IN_PROGRESS=EINPROGRESS \ -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/include \ - -I$(LOCAL_PATH)/libudns \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/mbedtls/include \ @@ -307,7 +286,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ -I$(LOCAL_PATH)/shadowsocks-libev/libbloom \ -I$(LOCAL_PATH)/include/shadowsocks-libev -LOCAL_STATIC_LIBRARIES := libev libmbedtls libsodium libcork libbloom libudns libancillary +LOCAL_STATIC_LIBRARIES := libev libmbedtls libsodium libcork libbloom libancillary LOCAL_LDLIBS := -llog diff --git a/mobile/src/main/jni/libudns b/mobile/src/main/jni/libudns deleted file mode 160000 index 53a1abd2b6..0000000000 --- a/mobile/src/main/jni/libudns +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53a1abd2b6fd7e3bf99ce6e2e267c636618d57ef diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 72622a3e3d..666801ddfc 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 72622a3e3df0d8b66f005717ecc48264275f453c +Subproject commit 666801ddfcf70ebc7418b238129c9bfaa9325b44 From 2cae9384ee979d7bcecb85497c1e899a8fc5ffa9 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 24 Sep 2017 21:23:08 -0700 Subject: [PATCH 04/79] Fix #1387 --- .../main/scala/com/github/shadowsocks/ProfilesFragment.scala | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 618a7931d9..75f7557475 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -358,7 +358,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic REQUEST_SCAN_QR_CODE) catch { case _: ActivityNotFoundException => startActivity(new Intent(getActivity, classOf[ScannerActivity])) - case e: SecurityException => + case e: Exception => e.printStackTrace() app.track(e) startActivity(new Intent(getActivity, classOf[ScannerActivity])) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1bd77bd93f..bf868f42f8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.9") +addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.10") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") From 2b787d50722fde71c5104d5b573851d60d9074d7 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 24 Sep 2017 21:37:35 -0700 Subject: [PATCH 05/79] Update golang --- mobile/src/overture/go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/overture/go b/mobile/src/overture/go index 26db64c766..b68d7f897c 160000 --- a/mobile/src/overture/go +++ b/mobile/src/overture/go @@ -1 +1 @@ -Subproject commit 26db64c76604b35e756f593d8ce25da5a83af6b6 +Subproject commit b68d7f897c6800363f855cface600c82749a4845 From 918eec43944128685264ab5a02a1962d32bcea9a Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 3 Oct 2017 07:01:18 -0700 Subject: [PATCH 06/79] Revert to previous AdView --- .../github/shadowsocks/ProfilesFragment.scala | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 75f7557475..38916cd47f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -40,7 +40,7 @@ import com.github.shadowsocks.database.Profile import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.utils._ import com.github.shadowsocks.widget.UndoSnackbarManager -import com.google.android.gms.ads.{AdRequest, AdSize, NativeExpressAdView} +import com.google.android.gms.ads.{AdRequest, AdSize, AdView} import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.util.Random @@ -88,7 +88,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic // it will not take effect unless set in code itemView.findViewById[View](R.id.indicator).setBackgroundResource(R.drawable.background_profile) - private var adView: NativeExpressAdView = _ + private var adView: AdView = _ { val share = itemView.findViewById[View](R.id.share) @@ -143,22 +143,14 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) params.gravity = Gravity.CENTER_HORIZONTAL params.setMargins(0, getResources.getDimensionPixelOffset(R.dimen.margin_small), 0, 0) - adView = new NativeExpressAdView(getActivity) + adView = new AdView(getActivity) adView.setLayoutParams(params) - adView.setAdUnitId("ca-app-pub-9097031975646651/5224027521") - adView.setAdSize(new AdSize(328, 132)) + adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") + adView.setAdSize(AdSize.LARGE_BANNER) itemView.findViewById[LinearLayout](R.id.content).addView(adView) - // Demographics - val random = new Random() - val adBuilder = new AdRequest.Builder() - adBuilder.setGender(AdRequest.GENDER_MALE) - val year = 1975 + random.nextInt(40) - val month = 1 + random.nextInt(12) - val day = random.nextInt(28) - adBuilder.setBirthday(new GregorianCalendar(year, month, day).getTime) - // Load Ad + val adBuilder = new AdRequest.Builder() adView.loadAd(adBuilder.build()) } else adView.setVisibility(View.VISIBLE) } else if (adView != null) adView.setVisibility(View.GONE) From a2df7a7efa53740d631851f9b5195eae5a30f8ed Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 10:47:28 -0700 Subject: [PATCH 07/79] Add Xposed related issues to FAQ Fixes #1414. --- .github/faq.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/faq.md b/.github/faq.md index 6a6eee829c..4013558e47 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -29,13 +29,15 @@ Scan it with a third-party scanner like [QuickMark Barcode Scanner](https://play The exclamation mark in the Wi-Fi/cellular icon appears because the system fails to connect to portal server (defaults to `clients3.google.com`) without VPN connection. To remove it, follow the instructions in [this article](https://www.noisyfox.cn/45.html). (in Simplified Chinese) -### Why are MIUI, EMUI and other AOSPs in China not officially supported? +### Why is my ROM not supported? -1. Broken VPNService implementation, especially for IPv6; -2. Aggressive (or called broken) background service killing policy. +1. Some ROM has broken VPNService implementation, especially for IPv6; +2. Some ROM has aggressive (or called broken) background service killing policy; +3. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either. * Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) * Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) +* Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414) ### How to pause Shadowsocks service? @@ -59,3 +61,5 @@ Allow this app to consume background data in app settings. To scan the QR code through the integrated QR scanner. +By the way, upgrade your Android system already. + From c1cd7e2ca1e22e582996c1124e26dcde5fde6bab Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 11:05:39 -0700 Subject: [PATCH 08/79] Fix #1410 --- .github/faq.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/faq.md b/.github/faq.md index 4013558e47..19a1cb8f19 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -38,6 +38,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails * Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) * Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) * Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414) +* Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410) ### How to pause Shadowsocks service? From 55ab8b4be7838a898b51761ca62e2b556b0a7784 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 20:15:54 -0700 Subject: [PATCH 09/79] Update dependencies --- build.sbt | 2 +- mobile/build.sbt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index b295eca219..cca671ae29 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ lazy val commonSettings = Seq( resolvers += "google" at "https://maven.google.com" ) -val supportLibsVersion = "26.0.0" +val supportLibsVersion = "26.1.0" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) diff --git a/mobile/build.sbt b/mobile/build.sbt index 2606d21b26..93d0afa33b 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -23,11 +23,11 @@ proguardOptions ++= "-keep public class com.evernote.android.job.JobRescheduleService" :: Nil -val playServicesVersion = "11.2.0" +val playServicesVersion = "11.4.2" resolvers += Resolver.jcenterRepo libraryDependencies ++= "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: - "com.evernote" % "android-job" % "1.2.0-alpha4" :: + "com.evernote" % "android-job" % "1.2.0" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.google.android.gms" % "play-services-ads" % playServicesVersion :: "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: @@ -39,7 +39,7 @@ libraryDependencies ++= "com.mikepenz" % "iconics-core" % "2.9.3" :: "com.mikepenz" % "materialdrawer" % "5.9.5" :: "com.mikepenz" % "materialize" % "1.0.3" :: - "com.squareup.okhttp3" % "okhttp" % "3.8.1" :: + "com.squareup.okhttp3" % "okhttp" % "3.9.0" :: "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: "dnsjava" % "dnsjava" % "2.1.8" :: "eu.chainfire" % "libsuperuser" % "1.0.0.201704021214" :: From 81b7add2dddaf797d36004077dac3b7d7a46d5d7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 20:49:38 -0700 Subject: [PATCH 10/79] Improve go-clean script --- mobile/build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/build.sbt b/mobile/build.sbt index 93d0afa33b..3ea4a895d2 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -57,6 +57,8 @@ packagingOptions := PackagingOptions(excludes = lazy val goClean = TaskKey[Unit]("go-clean", "Clean go build dependencies") goClean := { IO.delete(baseDirectory(base => base / "src/overture/.deps").value) + IO.delete(baseDirectory(base => base / "src/overture/bin").value) + IO.delete(baseDirectory(base => base / "src/overture/go/bin").value) IO.delete(baseDirectory(base => base / "src/main/jni/overture").value) } From bc0b50309890f7154ce3e8831cd9117609936813 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 20:57:05 -0700 Subject: [PATCH 11/79] Fix DNS leakage Fix #1397. Tested via dnsleaktest.com. --- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 242f4460ea..20a3c6d5ae 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -186,7 +186,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { .setMtu(VPN_MTU) .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) - builder.addDnsServer("8.8.8.8") // It's fake DNS for tun2socks, not the real remote DNS + profile.remoteDns.split(",").foreach(dns => builder.addDnsServer(dns.trim)) if (profile.ipv6) { builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126) From 9075d079d25fc570503d03261607b84ed0542c6f Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Oct 2017 20:59:24 -0700 Subject: [PATCH 12/79] Remove support for external scanners I decided to remove this feature because I saw that Google Authenticator is doing the same: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2 Also fix #1407. --- mobile/src/main/res/values/strings.xml | 1 - .../github/shadowsocks/ProfilesFragment.scala | 28 +------------------ 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index c1f558ca04..09e252b8bb 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -105,7 +105,6 @@ Add this Shadowsocks Profile? Scan QR code Manual Settings - Please install any ZXing-compliant QR code scanning app. Camera permission is required for scanning QR code. Removed diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 38916cd47f..796f27a9d1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -20,18 +20,13 @@ package com.github.shadowsocks -import java.util.GregorianCalendar - -import android.app.Activity import android.content._ -import android.net.Uri import android.os.Bundle import android.support.design.widget.Snackbar import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget._ import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback -import android.text.TextUtils import android.view.View.OnLongClickListener import android.view._ import android.widget.{LinearLayout, PopupMenu, TextView, Toast} @@ -43,12 +38,9 @@ import com.github.shadowsocks.widget.UndoSnackbarManager import com.google.android.gms.ads.{AdRequest, AdSize, AdView} import scala.collection.mutable.{ArrayBuffer, ListBuffer} -import scala.util.Random object ProfilesFragment { var instance: ProfilesFragment = _ // used for callback from ProfileManager and stateChanged from MainActivity - - private final val REQUEST_SCAN_QR_CODE = 0 } final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickListener { @@ -334,27 +326,9 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic super.onDestroy() } - override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { - case REQUEST_SCAN_QR_CODE => if (resultCode == Activity.RESULT_OK) { - val contents = data.getStringExtra("SCAN_RESULT") - if (!TextUtils.isEmpty(contents)) Parser.findAll(contents).foreach(app.profileManager.createProfile) - } - case _ => super.onActivityResult(resultCode, resultCode, data) - } - def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { case R.id.action_scan_qr_code => - try startActivityForResult(new Intent("com.google.zxing.client.android.SCAN") - .addCategory(Intent.CATEGORY_DEFAULT) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_DOCUMENT), - REQUEST_SCAN_QR_CODE) catch { - case _: ActivityNotFoundException => - startActivity(new Intent(getActivity, classOf[ScannerActivity])) - case e: Exception => - e.printStackTrace() - app.track(e) - startActivity(new Intent(getActivity, classOf[ScannerActivity])) - } + startActivity(new Intent(getActivity, classOf[ScannerActivity])) true case R.id.action_import => try { From e79a93545b583d28ac4f9f381decda5e24b09bb0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 25 Oct 2017 16:57:44 -0700 Subject: [PATCH 13/79] Fix the magic bug in sbt https://stackoverflow.com/a/24860931/2245107 --- mobile/build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.sbt b/mobile/build.sbt index 3ea4a895d2..211604cbf5 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -54,7 +54,7 @@ packagingOptions := PackagingOptions(excludes = "META-INF/maven/com.squareup.okhttp3/okhttp/pom.xml" :: Nil) -lazy val goClean = TaskKey[Unit]("go-clean", "Clean go build dependencies") +lazy val goClean: TaskKey[Unit] = TaskKey[Unit]("go-clean", "Clean go build dependencies") goClean := { IO.delete(baseDirectory(base => base / "src/overture/.deps").value) IO.delete(baseDirectory(base => base / "src/overture/bin").value) @@ -62,7 +62,7 @@ goClean := { IO.delete(baseDirectory(base => base / "src/main/jni/overture").value) } -lazy val goBuild = TaskKey[Unit]("go-build", "Build go and overture") +lazy val goBuild: TaskKey[Unit] = TaskKey[Unit]("go-build", "Build go and overture") goBuild := { Process(Seq("mobile/src/overture/make.bash", minSdkVersion.value)) ! streams.value.log match { case 0 => // Success! From 6fa1c822ef7a700ccafc2777b78ac5b670feec11 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 28 Oct 2017 00:16:05 -0700 Subject: [PATCH 14/79] Update platform target to 8.1 (API level 27) --- build.sbt | 4 ++-- mobile/src/main/AndroidManifest.xml | 2 +- plugin/src/main/AndroidManifest.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index cca671ae29..40185dbe74 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ lazy val commonSettings = Seq( organization := "com.github.shadowsocks", - platformTarget := "android-26", + platformTarget := "android-27", compileOrder := CompileOrder.JavaThenScala, javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil, @@ -23,7 +23,7 @@ lazy val commonSettings = Seq( resolvers += "google" at "https://maven.google.com" ) -val supportLibsVersion = "26.1.0" +val supportLibsVersion = "27.0.0" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 4f61094046..fbbe3bff59 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ + android:targetSdkVersion="27"/> diff --git a/plugin/src/main/AndroidManifest.xml b/plugin/src/main/AndroidManifest.xml index ca89bcc9ce..25974a7025 100644 --- a/plugin/src/main/AndroidManifest.xml +++ b/plugin/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ package="com.github.shadowsocks.plugin"> + android:targetSdkVersion="27"/> Date: Sat, 28 Oct 2017 00:17:36 -0700 Subject: [PATCH 15/79] Update travis to use build tools 27 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 88ab7c8a22..cb373aa2df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ cache: android: components: - tools - - build-tools-26.0.0 + - build-tools-27.0.0 - extra-android-m2repository - extra-google-m2repository From 4292b177ad02e9bb0ad7cca1539a12e37e578f8b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 28 Oct 2017 11:45:39 -0700 Subject: [PATCH 16/79] Update faq.md --- .github/faq.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/faq.md b/.github/faq.md index 19a1cb8f19..eb6dcad62c 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -39,6 +39,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails * Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) * Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414) * Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410) +* Don't install this app on SD card because of permission issues: [#1124 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453) ### How to pause Shadowsocks service? From 6c49a1aa4e3e4e19d183fc37cda08d59ace57aba Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 8 Nov 2017 23:54:06 -0800 Subject: [PATCH 17/79] Bump version --- mobile/build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.sbt b/mobile/build.sbt index 211604cbf5..d89a4338e4 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -4,8 +4,8 @@ enablePlugins(AndroidGms) android.useSupportVectors name := "shadowsocks" -version := "4.2.5" -versionCode := Some(195) +version := "4.3.0" +versionCode := Some(196) proguardOptions ++= "-dontwarn com.google.android.gms.internal.**" :: From 7fb19c40af3cffc399e7709aa2e09b7a20b175f5 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 9 Nov 2017 19:02:38 -0800 Subject: [PATCH 18/79] Replace NAT mode with local SOCKS5 mode. #1441 --- .gitmodules | 8 - mobile/src/main/AndroidManifest.xml | 4 +- mobile/src/main/jni/Android.mk | 48 ---- mobile/src/main/jni/libevent | 1 - mobile/src/main/jni/redsocks | 1 - mobile/src/main/res/values/strings.xml | 12 +- mobile/src/main/res/xml/pref_global.xml | 4 +- .../com/github/shadowsocks/MainActivity.scala | 12 +- .../shadowsocks/ProfileConfigFragment.scala | 2 +- .../shadowsocks/ServiceBoundContext.scala | 2 +- .../shadowsocks/ShadowsocksApplication.scala | 14 +- .../shadowsocks/ShadowsocksLocalService.scala | 121 +++++++++ .../shadowsocks/ShadowsocksNatService.scala | 231 ------------------ .../ShadowsocksRunnerActivity.scala | 2 +- .../com/github/shadowsocks/utils/Utils.scala | 4 +- 15 files changed, 141 insertions(+), 325 deletions(-) delete mode 160000 mobile/src/main/jni/libevent delete mode 160000 mobile/src/main/jni/redsocks create mode 100644 mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala diff --git a/.gitmodules b/.gitmodules index 09136bb10a..ca93a282ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,14 +9,6 @@ path = mobile/src/main/jni/libancillary url = https://github.com/shadowsocks/libancillary.git branch = shadowsocks-android -[submodule "mobile/src/main/jni/libevent"] - path = mobile/src/main/jni/libevent - url = https://github.com/shadowsocks/libevent.git - branch = shadowsocks-android -[submodule "mobile/src/main/jni/redsocks"] - path = mobile/src/main/jni/redsocks - url = https://github.com/shadowsocks/redsocks.git - branch = shadowsocks-android [submodule "mobile/src/main/jni/mbedtls"] path = mobile/src/main/jni/mbedtls url = https://github.com/ARMmbed/mbedtls diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index fbbe3bff59..c3ef349983 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:required="false"/> - diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index ccc3b4738b..303c88cc5c 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -78,32 +78,6 @@ LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE)) include $(BUILD_STATIC_LIBRARY) -######################################################## -## libevent -######################################################## - -include $(CLEAR_VARS) - -LIBEVENT_SOURCES := \ - buffer.c \ - bufferevent.c bufferevent_filter.c \ - bufferevent_pair.c bufferevent_ratelim.c \ - bufferevent_sock.c epoll.c \ - epoll_sub.c evdns.c event.c \ - event_tagging.c evmap.c \ - evrpc.c evthread.c \ - evthread_pthread.c evutil.c \ - evutil_rand.c http.c \ - listener.c log.c poll.c \ - select.c signal.c strlcpy.c - -LOCAL_MODULE := event -LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) -LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \ - -I$(LOCAL_PATH)/libevent/include \ - -include $(BUILD_STATIC_LIBRARY) - ######################################################## ## libancillary ######################################################## @@ -200,28 +174,6 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) -######################################################## -## redsocks -######################################################## - -include $(CLEAR_VARS) - -REDSOCKS_SOURCES := base.c http-connect.c \ - log.c md5.c socks5.c \ - base64.c http-auth.c http-relay.c main.c \ - parser.c redsocks.c socks4.c utils.c - -LOCAL_STATIC_LIBRARIES := libevent - -LOCAL_MODULE := redsocks -LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES)) -LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ - -I$(LOCAL_PATH)/redsocks \ - -I$(LOCAL_PATH)/libevent/include \ - -I$(LOCAL_PATH)/libevent - -include $(BUILD_SHARED_EXECUTABLE) - ######################################################## ## shadowsocks-libev local ######################################################## diff --git a/mobile/src/main/jni/libevent b/mobile/src/main/jni/libevent deleted file mode 160000 index 359ca847a6..0000000000 --- a/mobile/src/main/jni/libevent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 359ca847a649b9c318f9217c0755484d98ecb779 diff --git a/mobile/src/main/jni/redsocks b/mobile/src/main/jni/redsocks deleted file mode 160000 index 274334f148..0000000000 --- a/mobile/src/main/jni/redsocks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 274334f14839431ae003774d99c3d1de337afff4 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 09e252b8bb..ed44e500aa 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -7,8 +7,8 @@ Profile Switch to another profile or add new profiles - NAT mode (deprecated) - Use NAT mode instead of VPN mode. Requires ROOT permission. + SOCKS5 mode + Enable SOCKS5 proxy mode instead of VPN mode to work with AFWall+ or Orbot Remote DNS Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s %1$s↑\t%2$s↓ @@ -43,7 +43,7 @@ GFW List China List Per-App Proxy - Set proxy for selected apps, requires NAT mode under Android 4.x + Set proxy for selected apps Set proxy for selected apps On Bypass Mode @@ -57,12 +57,10 @@ VPN Service - NAT Service + SOCKS5 Service Shadowsocks started. Invalid server name Failed to connect the remote server - WARNING: NAT mode has been deprecated since Android 5.0 - NAT mode requires ROOT permission Switch to VPN mode Stop Shutting down… @@ -124,7 +122,7 @@ Received: Connecting… Connected, tap to check connection - Connected + Connected Not connected diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index 7d54d22559..d6df49f7b4 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -8,6 +8,6 @@ android:summary="@string/tcp_fastopen_summary" android:title="TCP Fast Open"/> + android:title="@string/local" + android:summary="@string/local_summary"/> diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 9450aac633..5bef0c68b8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -117,7 +117,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.postDelayed(hideCircle, 1000) fab.setImageResource(R.drawable.ic_start_connected) - statusText.setText(if (app.isNatEnabled) R.string.nat_connected else R.string.vpn_connected) + statusText.setText(if (app.isLocalEnabled) R.string.local_connected else R.string.vpn_connected) case State.STOPPING => fab.setImageResource(R.drawable.ic_start_busy) if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped @@ -128,7 +128,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (m != null) { val snackbar = Snackbar.make(findViewById(R.id.snackbar), getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG) - if (m == getString(R.string.nat_no_root)) addDisableNatToSnackbar(snackbar) snackbar.show() Log.e(TAG, "Error to start VPN service: " + m) } @@ -157,16 +156,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onServiceConnected() { changeState(bgService.getState) - if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) { - val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG) - addDisableNatToSnackbar(snackbar) - snackbar.show() - } } override def onServiceDisconnected(): Unit = changeState(State.IDLE) - private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => - if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener) override def binderDied(): Unit = handler.post(() => { detachService() @@ -300,7 +292,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture { - if (app.isNatEnabled) Utils.startSsService(this) else { + if (app.isLocalEnabled) Utils.startSsService(this) else { val intent = VpnService.prepare(this) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 16cdf902a0..1f304e6d0d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe findPreference(Key.password).setSummary("\u2022" * 32) } isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled) + isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isLocalEnabled) isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(getActivity, classOf[AppManager])) isProxyApps.setChecked(true) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index f588ff2c7c..4f4ab15c83 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -85,7 +85,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) { this.callback = callback if (bgService == null) { - val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] + val s = if (app.isLocalEnabled) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService] val intent = new Intent(this, s) intent.setAction(Action.SERVICE) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index d8419e0ca1..296e320df8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -66,8 +66,8 @@ class ShadowsocksApplication extends Application { lazy val profileManager = new ProfileManager(dbHelper) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper) - def isNatEnabled: Boolean = dataStore.isNAT - def isVpnEnabled: Boolean = !isNatEnabled + def isLocalEnabled: Boolean = dataStore.isNAT + def isVpnEnabled: Boolean = !isLocalEnabled // send event def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder() @@ -156,7 +156,7 @@ class ShadowsocksApplication extends Application { if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), - new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW) + new NotificationChannel("service-local", getText(R.string.service_local), NotificationManager.IMPORTANCE_LOW) )) } @@ -165,15 +165,9 @@ class ShadowsocksApplication extends Application { for (task <- Executable.EXECUTABLES) { cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) - cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" + cmd.append("rm -f %1$s/%2$s-local.conf %1$s/%2$s-vpn.conf" .formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) } - if (app.isNatEnabled) { - cmd.append("iptables -t nat -F OUTPUT") - cmd.append("echo done") - val result = Shell.SU.run(cmd.toArray) - if (result != null && !result.isEmpty) return // fallback to SH - } Shell.SH.run(cmd.toArray) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala new file mode 100644 index 0000000000..248cd5e66f --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala @@ -0,0 +1,121 @@ +/*******************************************************************************/ +/* */ +/* Copyright (C) 2017 by Max Lv */ +/* Copyright (C) 2017 by Mygod Studio */ +/* */ +/* This program is free software: you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation, either version 3 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/* */ +/*******************************************************************************/ + +package com.github.shadowsocks + +import java.io.File +import java.net.{Inet6Address, InetAddress} +import java.util.Locale + +import android.app.Service +import android.content._ +import android.os._ +import android.util.Log +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.acl.{Acl, AclSyncJob} +import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.utils._ + +import scala.collection.JavaConversions._ +import scala.collection.mutable.ArrayBuffer + +class ShadowsocksLocalService extends BaseService { + + val TAG = "ShadowsocksLocalService" + + var sslocalProcess: GuardedProcess = _ + + def startShadowsocksDaemon() { + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", + "-b", "127.0.0.1", + "-l", profile.localPort.toString, + "-t", "600", + "-c", buildShadowsocksConfig("ss-local-local.conf")) + + if (TcpFastOpen.sendEnabled) cmd += "--fast-open" + + if (profile.route != Acl.ALL) { + cmd += "--acl" + cmd += Acl.getFile(profile.route match { + case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED + case route => route + }).getAbsolutePath + } + + sslocalProcess = new GuardedProcess(cmd: _*).start() + } + + /** Called when the activity is first created. */ + def handleConnection() { + + startShadowsocksDaemon() + + } + + def onBind(intent: Intent): IBinder = { + Log.d(TAG, "onBind") + if (Action.SERVICE == intent.getAction) { + binder + } else { + null + } + } + + def killProcesses() { + if (sslocalProcess != null) { + sslocalProcess.destroy() + sslocalProcess = null + } + } + + override def connect() { + super.connect() + + // Clean up + killProcesses() + + if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { + case Some(a) => profile.host = a + case None => throw NameNotResolvedException() + } + + handleConnection() + + if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) + AclSyncJob.schedule(profile.route) + + changeState(State.CONNECTED) + } + + override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-local", true) + + override def stopRunner(stopService: Boolean, msg: String = null) { + + // channge the state + changeState(State.STOPPING) + + app.track(TAG, "stop") + + // reset NAT + killProcesses() + + super.stopRunner(stopService, msg) + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala deleted file mode 100644 index dd915eb39c..0000000000 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ /dev/null @@ -1,231 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package com.github.shadowsocks - -import java.io.File -import java.net.{Inet6Address, InetAddress} -import java.util.Locale - -import android.app.Service -import android.content._ -import android.os._ -import android.util.Log -import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob} -import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.utils._ -import eu.chainfire.libsuperuser.Shell - -import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer - -class ShadowsocksNatService extends BaseService { - - val TAG = "ShadowsocksNatService" - - val CMD_IPTABLES_DNAT_ADD_SOCKS = - "iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123" - - val myUid: Int = android.os.Process.myUid() - - var sslocalProcess: GuardedProcess = _ - var sstunnelProcess: GuardedProcess = _ - var redsocksProcess: GuardedProcess = _ - var overtureProcess: GuardedProcess = _ - var su: Shell.Interactive = _ - - def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", - "-b", "127.0.0.1", - "-l", profile.localPort.toString, - "-t", "600", - "-c", buildShadowsocksConfig("ss-local-nat.conf")) - - if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - - if (profile.route != Acl.ALL) { - cmd += "--acl" - cmd += Acl.getFile(profile.route match { - case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED - case route => route - }).getAbsolutePath - } - - sslocalProcess = new GuardedProcess(cmd: _*).start() - } - - def startDNSTunnel() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", - "-t", "10", - "-b", "127.0.0.1", - "-l", (profile.localPort + 63).toString, - "-L", profile.remoteDns.split(",").head.trim + ":53", - "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) - - if (profile.udpdns) cmd.append("-u") - - sstunnelProcess = new GuardedProcess(cmd: _*).start() - } - - def startDnsDaemon() { - overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", - "-c", buildOvertureConfig("overture-nat.conf")) - .start() - } - - def startRedsocksDaemon() { - IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"), - ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)) - redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", - "-c", "redsocks-nat.conf").start() - } - - /** Called when the activity is first created. */ - def handleConnection() { - - startRedsocksDaemon() - startShadowsocksDaemon() - - if (!profile.udpdns) - startDnsDaemon() - else - startDNSTunnel() - - setupIptables() - - } - - def onBind(intent: Intent): IBinder = { - Log.d(TAG, "onBind") - if (Action.SERVICE == intent.getAction) { - binder - } else { - null - } - } - - def killProcesses() { - if (sslocalProcess != null) { - sslocalProcess.destroy() - sslocalProcess = null - } - if (sstunnelProcess != null) { - sstunnelProcess.destroy() - sstunnelProcess = null - } - if (redsocksProcess != null) { - redsocksProcess.destroy() - redsocksProcess = null - } - if (overtureProcess != null) { - overtureProcess.destroy() - overtureProcess = null - } - - su.addCommand("iptables -t nat -F OUTPUT") - } - - def setupIptables() { - val init_sb = new ArrayBuffer[String] - val http_sb = new ArrayBuffer[String] - - init_sb.append("ulimit -n 4096") - init_sb.append("iptables -t nat -F OUTPUT") - - val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" - if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) - } - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1")) - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) - init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53")) - - init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" - + (profile.localPort + 53)) - - if (!profile.proxyApps || profile.bypass) { - http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS) - } - if (profile.proxyApps) { - val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap - for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { - case Some(uid) => - if (!profile.bypass) { - http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS - .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) - } else { - init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid)) - } - case _ => // probably removed package, ignore - } - } - su.addCommand((init_sb ++ http_sb).toArray) - } - - override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) { - su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) => - if (exitCode == 0) super.onStartCommand(intent, flags, startId) else { - if (su != null) { - su.close() - su = null - } - super.stopRunner(stopService = true, getString(R.string.nat_no_root)) - }) - Service.START_NOT_STICKY - } else super.onStartCommand(intent, flags, startId) - - override def connect() { - super.connect() - - // Clean up - killProcesses() - - if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { - case Some(a) => profile.host = a - case None => throw NameNotResolvedException() - } - - handleConnection() - - if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) - AclSyncJob.schedule(profile.route) - - changeState(State.CONNECTED) - } - - override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true) - - override def stopRunner(stopService: Boolean, msg: String = null) { - - // channge the state - changeState(State.STOPPING) - - app.track(TAG, "stop") - - // reset NAT - killProcesses() - - su.close() - su = null - - super.stopRunner(stopService, msg) - } -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index 9fd86e8e97..a1c5a9cf52 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -46,7 +46,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { } def startBackgroundService() { - if (app.isNatEnabled) { + if (app.isLocalEnabled) { Utils.startSsService(this) finish() } else { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index eae40a19a4..349ccc5870 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -33,7 +33,7 @@ import android.view.View.MeasureSpec import android.view.{Gravity, View, Window} import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService} +import com.github.shadowsocks.{BuildConfig, ShadowsocksLocalService, ShadowsocksVpnService} import org.xbill.DNS._ import scala.collection.JavaConversions._ @@ -138,7 +138,7 @@ object Utils { def startSsService(context: Context) { val intent = - new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]) + new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService]) if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent) } def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD)) From 6678fd9541e280963d6680d567c12c4d69e846ef Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 10 Nov 2017 19:31:55 -0800 Subject: [PATCH 19/79] Revert "Replace NAT mode with local SOCKS5 mode. #1441" This reverts commit 7fb19c40af3cffc399e7709aa2e09b7a20b175f5. --- .gitmodules | 8 + mobile/src/main/AndroidManifest.xml | 4 +- mobile/src/main/jni/Android.mk | 48 ++++ mobile/src/main/jni/libevent | 1 + mobile/src/main/jni/redsocks | 1 + mobile/src/main/res/values/strings.xml | 12 +- mobile/src/main/res/xml/pref_global.xml | 4 +- .../com/github/shadowsocks/MainActivity.scala | 12 +- .../shadowsocks/ProfileConfigFragment.scala | 2 +- .../shadowsocks/ServiceBoundContext.scala | 2 +- .../shadowsocks/ShadowsocksApplication.scala | 14 +- .../shadowsocks/ShadowsocksLocalService.scala | 121 --------- .../shadowsocks/ShadowsocksNatService.scala | 231 ++++++++++++++++++ .../ShadowsocksRunnerActivity.scala | 2 +- .../com/github/shadowsocks/utils/Utils.scala | 4 +- 15 files changed, 325 insertions(+), 141 deletions(-) create mode 160000 mobile/src/main/jni/libevent create mode 160000 mobile/src/main/jni/redsocks delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala diff --git a/.gitmodules b/.gitmodules index ca93a282ff..09136bb10a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,6 +9,14 @@ path = mobile/src/main/jni/libancillary url = https://github.com/shadowsocks/libancillary.git branch = shadowsocks-android +[submodule "mobile/src/main/jni/libevent"] + path = mobile/src/main/jni/libevent + url = https://github.com/shadowsocks/libevent.git + branch = shadowsocks-android +[submodule "mobile/src/main/jni/redsocks"] + path = mobile/src/main/jni/redsocks + url = https://github.com/shadowsocks/redsocks.git + branch = shadowsocks-android [submodule "mobile/src/main/jni/mbedtls"] path = mobile/src/main/jni/mbedtls url = https://github.com/ARMmbed/mbedtls diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index c3ef349983..fbbe3bff59 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:required="false"/> - diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 303c88cc5c..ccc3b4738b 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -78,6 +78,32 @@ LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE)) include $(BUILD_STATIC_LIBRARY) +######################################################## +## libevent +######################################################## + +include $(CLEAR_VARS) + +LIBEVENT_SOURCES := \ + buffer.c \ + bufferevent.c bufferevent_filter.c \ + bufferevent_pair.c bufferevent_ratelim.c \ + bufferevent_sock.c epoll.c \ + epoll_sub.c evdns.c event.c \ + event_tagging.c evmap.c \ + evrpc.c evthread.c \ + evthread_pthread.c evutil.c \ + evutil_rand.c http.c \ + listener.c log.c poll.c \ + select.c signal.c strlcpy.c + +LOCAL_MODULE := event +LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) +LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \ + -I$(LOCAL_PATH)/libevent/include \ + +include $(BUILD_STATIC_LIBRARY) + ######################################################## ## libancillary ######################################################## @@ -174,6 +200,28 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) +######################################################## +## redsocks +######################################################## + +include $(CLEAR_VARS) + +REDSOCKS_SOURCES := base.c http-connect.c \ + log.c md5.c socks5.c \ + base64.c http-auth.c http-relay.c main.c \ + parser.c redsocks.c socks4.c utils.c + +LOCAL_STATIC_LIBRARIES := libevent + +LOCAL_MODULE := redsocks +LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES)) +LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ + -I$(LOCAL_PATH)/redsocks \ + -I$(LOCAL_PATH)/libevent/include \ + -I$(LOCAL_PATH)/libevent + +include $(BUILD_SHARED_EXECUTABLE) + ######################################################## ## shadowsocks-libev local ######################################################## diff --git a/mobile/src/main/jni/libevent b/mobile/src/main/jni/libevent new file mode 160000 index 0000000000..359ca847a6 --- /dev/null +++ b/mobile/src/main/jni/libevent @@ -0,0 +1 @@ +Subproject commit 359ca847a649b9c318f9217c0755484d98ecb779 diff --git a/mobile/src/main/jni/redsocks b/mobile/src/main/jni/redsocks new file mode 160000 index 0000000000..274334f148 --- /dev/null +++ b/mobile/src/main/jni/redsocks @@ -0,0 +1 @@ +Subproject commit 274334f14839431ae003774d99c3d1de337afff4 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index ed44e500aa..09e252b8bb 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -7,8 +7,8 @@ Profile Switch to another profile or add new profiles - SOCKS5 mode - Enable SOCKS5 proxy mode instead of VPN mode to work with AFWall+ or Orbot + NAT mode (deprecated) + Use NAT mode instead of VPN mode. Requires ROOT permission. Remote DNS Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s %1$s↑\t%2$s↓ @@ -43,7 +43,7 @@ GFW List China List Per-App Proxy - Set proxy for selected apps + Set proxy for selected apps, requires NAT mode under Android 4.x Set proxy for selected apps On Bypass Mode @@ -57,10 +57,12 @@ VPN Service - SOCKS5 Service + NAT Service Shadowsocks started. Invalid server name Failed to connect the remote server + WARNING: NAT mode has been deprecated since Android 5.0 + NAT mode requires ROOT permission Switch to VPN mode Stop Shutting down… @@ -122,7 +124,7 @@ Received: Connecting… Connected, tap to check connection - Connected + Connected Not connected diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index d6df49f7b4..7d54d22559 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -8,6 +8,6 @@ android:summary="@string/tcp_fastopen_summary" android:title="TCP Fast Open"/> + android:title="@string/nat" + android:summary="@string/nat_summary"/> diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 5bef0c68b8..9450aac633 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -117,7 +117,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.postDelayed(hideCircle, 1000) fab.setImageResource(R.drawable.ic_start_connected) - statusText.setText(if (app.isLocalEnabled) R.string.local_connected else R.string.vpn_connected) + statusText.setText(if (app.isNatEnabled) R.string.nat_connected else R.string.vpn_connected) case State.STOPPING => fab.setImageResource(R.drawable.ic_start_busy) if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped @@ -128,6 +128,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (m != null) { val snackbar = Snackbar.make(findViewById(R.id.snackbar), getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG) + if (m == getString(R.string.nat_no_root)) addDisableNatToSnackbar(snackbar) snackbar.show() Log.e(TAG, "Error to start VPN service: " + m) } @@ -156,9 +157,16 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onServiceConnected() { changeState(bgService.getState) + if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) { + val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG) + addDisableNatToSnackbar(snackbar) + snackbar.show() + } } override def onServiceDisconnected(): Unit = changeState(State.IDLE) + private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => + if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener) override def binderDied(): Unit = handler.post(() => { detachService() @@ -292,7 +300,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture { - if (app.isLocalEnabled) Utils.startSsService(this) else { + if (app.isNatEnabled) Utils.startSsService(this) else { val intent = VpnService.prepare(this) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 1f304e6d0d..16cdf902a0 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe findPreference(Key.password).setSummary("\u2022" * 32) } isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isLocalEnabled) + isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled) isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(getActivity, classOf[AppManager])) isProxyApps.setChecked(true) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index 4f4ab15c83..f588ff2c7c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -85,7 +85,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) { this.callback = callback if (bgService == null) { - val s = if (app.isLocalEnabled) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService] + val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] val intent = new Intent(this, s) intent.setAction(Action.SERVICE) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 296e320df8..d8419e0ca1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -66,8 +66,8 @@ class ShadowsocksApplication extends Application { lazy val profileManager = new ProfileManager(dbHelper) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper) - def isLocalEnabled: Boolean = dataStore.isNAT - def isVpnEnabled: Boolean = !isLocalEnabled + def isNatEnabled: Boolean = dataStore.isNAT + def isVpnEnabled: Boolean = !isNatEnabled // send event def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder() @@ -156,7 +156,7 @@ class ShadowsocksApplication extends Application { if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), - new NotificationChannel("service-local", getText(R.string.service_local), NotificationManager.IMPORTANCE_LOW) + new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW) )) } @@ -165,9 +165,15 @@ class ShadowsocksApplication extends Application { for (task <- Executable.EXECUTABLES) { cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) - cmd.append("rm -f %1$s/%2$s-local.conf %1$s/%2$s-vpn.conf" + cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" .formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) } + if (app.isNatEnabled) { + cmd.append("iptables -t nat -F OUTPUT") + cmd.append("echo done") + val result = Shell.SU.run(cmd.toArray) + if (result != null && !result.isEmpty) return // fallback to SH + } Shell.SH.run(cmd.toArray) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala deleted file mode 100644 index 248cd5e66f..0000000000 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package com.github.shadowsocks - -import java.io.File -import java.net.{Inet6Address, InetAddress} -import java.util.Locale - -import android.app.Service -import android.content._ -import android.os._ -import android.util.Log -import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob} -import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.utils._ - -import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer - -class ShadowsocksLocalService extends BaseService { - - val TAG = "ShadowsocksLocalService" - - var sslocalProcess: GuardedProcess = _ - - def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", - "-b", "127.0.0.1", - "-l", profile.localPort.toString, - "-t", "600", - "-c", buildShadowsocksConfig("ss-local-local.conf")) - - if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - - if (profile.route != Acl.ALL) { - cmd += "--acl" - cmd += Acl.getFile(profile.route match { - case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED - case route => route - }).getAbsolutePath - } - - sslocalProcess = new GuardedProcess(cmd: _*).start() - } - - /** Called when the activity is first created. */ - def handleConnection() { - - startShadowsocksDaemon() - - } - - def onBind(intent: Intent): IBinder = { - Log.d(TAG, "onBind") - if (Action.SERVICE == intent.getAction) { - binder - } else { - null - } - } - - def killProcesses() { - if (sslocalProcess != null) { - sslocalProcess.destroy() - sslocalProcess = null - } - } - - override def connect() { - super.connect() - - // Clean up - killProcesses() - - if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { - case Some(a) => profile.host = a - case None => throw NameNotResolvedException() - } - - handleConnection() - - if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) - AclSyncJob.schedule(profile.route) - - changeState(State.CONNECTED) - } - - override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-local", true) - - override def stopRunner(stopService: Boolean, msg: String = null) { - - // channge the state - changeState(State.STOPPING) - - app.track(TAG, "stop") - - // reset NAT - killProcesses() - - super.stopRunner(stopService, msg) - } -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala new file mode 100644 index 0000000000..dd915eb39c --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -0,0 +1,231 @@ +/*******************************************************************************/ +/* */ +/* Copyright (C) 2017 by Max Lv */ +/* Copyright (C) 2017 by Mygod Studio */ +/* */ +/* This program is free software: you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation, either version 3 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/* */ +/*******************************************************************************/ + +package com.github.shadowsocks + +import java.io.File +import java.net.{Inet6Address, InetAddress} +import java.util.Locale + +import android.app.Service +import android.content._ +import android.os._ +import android.util.Log +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.acl.{Acl, AclSyncJob} +import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.utils._ +import eu.chainfire.libsuperuser.Shell + +import scala.collection.JavaConversions._ +import scala.collection.mutable.ArrayBuffer + +class ShadowsocksNatService extends BaseService { + + val TAG = "ShadowsocksNatService" + + val CMD_IPTABLES_DNAT_ADD_SOCKS = + "iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123" + + val myUid: Int = android.os.Process.myUid() + + var sslocalProcess: GuardedProcess = _ + var sstunnelProcess: GuardedProcess = _ + var redsocksProcess: GuardedProcess = _ + var overtureProcess: GuardedProcess = _ + var su: Shell.Interactive = _ + + def startShadowsocksDaemon() { + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", + "-b", "127.0.0.1", + "-l", profile.localPort.toString, + "-t", "600", + "-c", buildShadowsocksConfig("ss-local-nat.conf")) + + if (TcpFastOpen.sendEnabled) cmd += "--fast-open" + + if (profile.route != Acl.ALL) { + cmd += "--acl" + cmd += Acl.getFile(profile.route match { + case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED + case route => route + }).getAbsolutePath + } + + sslocalProcess = new GuardedProcess(cmd: _*).start() + } + + def startDNSTunnel() { + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", + "-t", "10", + "-b", "127.0.0.1", + "-l", (profile.localPort + 63).toString, + "-L", profile.remoteDns.split(",").head.trim + ":53", + "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) + + if (profile.udpdns) cmd.append("-u") + + sstunnelProcess = new GuardedProcess(cmd: _*).start() + } + + def startDnsDaemon() { + overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", + "-c", buildOvertureConfig("overture-nat.conf")) + .start() + } + + def startRedsocksDaemon() { + IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"), + ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)) + redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", + "-c", "redsocks-nat.conf").start() + } + + /** Called when the activity is first created. */ + def handleConnection() { + + startRedsocksDaemon() + startShadowsocksDaemon() + + if (!profile.udpdns) + startDnsDaemon() + else + startDNSTunnel() + + setupIptables() + + } + + def onBind(intent: Intent): IBinder = { + Log.d(TAG, "onBind") + if (Action.SERVICE == intent.getAction) { + binder + } else { + null + } + } + + def killProcesses() { + if (sslocalProcess != null) { + sslocalProcess.destroy() + sslocalProcess = null + } + if (sstunnelProcess != null) { + sstunnelProcess.destroy() + sstunnelProcess = null + } + if (redsocksProcess != null) { + redsocksProcess.destroy() + redsocksProcess = null + } + if (overtureProcess != null) { + overtureProcess.destroy() + overtureProcess = null + } + + su.addCommand("iptables -t nat -F OUTPUT") + } + + def setupIptables() { + val init_sb = new ArrayBuffer[String] + val http_sb = new ArrayBuffer[String] + + init_sb.append("ulimit -n 4096") + init_sb.append("iptables -t nat -F OUTPUT") + + val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" + if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) + } + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1")) + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) + init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53")) + + init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" + + (profile.localPort + 53)) + + if (!profile.proxyApps || profile.bypass) { + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS) + } + if (profile.proxyApps) { + val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap + for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { + case Some(uid) => + if (!profile.bypass) { + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS + .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) + } else { + init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid)) + } + case _ => // probably removed package, ignore + } + } + su.addCommand((init_sb ++ http_sb).toArray) + } + + override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) { + su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) => + if (exitCode == 0) super.onStartCommand(intent, flags, startId) else { + if (su != null) { + su.close() + su = null + } + super.stopRunner(stopService = true, getString(R.string.nat_no_root)) + }) + Service.START_NOT_STICKY + } else super.onStartCommand(intent, flags, startId) + + override def connect() { + super.connect() + + // Clean up + killProcesses() + + if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { + case Some(a) => profile.host = a + case None => throw NameNotResolvedException() + } + + handleConnection() + + if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) + AclSyncJob.schedule(profile.route) + + changeState(State.CONNECTED) + } + + override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true) + + override def stopRunner(stopService: Boolean, msg: String = null) { + + // channge the state + changeState(State.STOPPING) + + app.track(TAG, "stop") + + // reset NAT + killProcesses() + + su.close() + su = null + + super.stopRunner(stopService, msg) + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index a1c5a9cf52..9fd86e8e97 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -46,7 +46,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { } def startBackgroundService() { - if (app.isLocalEnabled) { + if (app.isNatEnabled) { Utils.startSsService(this) finish() } else { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 349ccc5870..eae40a19a4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -33,7 +33,7 @@ import android.view.View.MeasureSpec import android.view.{Gravity, View, Window} import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.{BuildConfig, ShadowsocksLocalService, ShadowsocksVpnService} +import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService} import org.xbill.DNS._ import scala.collection.JavaConversions._ @@ -138,7 +138,7 @@ object Utils { def startSsService(context: Context) { val intent = - new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService]) + new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]) if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent) } def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD)) From 725fb04f3f11abf5f99a4d16cf6c4b1600ac1e62 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 11 Nov 2017 00:06:58 -0800 Subject: [PATCH 20/79] Implement proxy mode and transproxy mode * NAT mode is replaced by transproxy mode; * BaseService classes are moved into bg package and refactored in order to minimize duplicate code; * Local port is moved out of per-profile settings, along with hardcoded DNS port and transproxy (redsocks) port; * Reset is removed since the only possible clean-up needed can be performed by using force stop in app info; * Comparing version code before extracting assets is replaced with a more robust approach: comparing update time. NB: the only code that involves su now is for toggling TFO. --- mobile/src/main/AndroidManifest.xml | 22 +- .../res/drawable/ic_navigation_refresh.xml | 9 - mobile/src/main/res/values/arrays.xml | 11 + mobile/src/main/res/values/strings.xml | 25 +- mobile/src/main/res/xml/pref_global.xml | 34 ++- mobile/src/main/res/xml/pref_profile.xml | 6 - .../shadowsocks/GlobalConfigFragment.scala | 42 +++- .../com/github/shadowsocks/MainActivity.scala | 85 +++---- .../shadowsocks/ProfileConfigFragment.scala | 2 +- .../github/shadowsocks/ProfilesFragment.scala | 9 +- .../shadowsocks/QuickToggleShortcut.scala | 7 +- .../shadowsocks/ServiceBoundContext.scala | 9 +- .../shadowsocks/ShadowsocksApplication.scala | 60 ++--- .../shadowsocks/ShadowsocksNatService.scala | 231 ------------------ .../ShadowsocksRunnerActivity.scala | 12 +- .../shadowsocks/acl/CustomRulesFragment.scala | 6 +- .../shadowsocks/{ => bg}/BaseService.scala | 231 +++++++++--------- .../github/shadowsocks/bg/Executable.scala | 27 ++ .../shadowsocks/bg/LocalDnsService.scala | 93 +++++++ .../github/shadowsocks/bg/ProxyService.scala | 12 + .../ServiceNotification.scala} | 12 +- .../github/shadowsocks/bg/ServiceState.scala | 16 ++ .../ServiceTileService.scala} | 16 +- .../{utils => bg}/TrafficMonitor.scala | 2 +- .../{utils => bg}/TrafficMonitorThread.scala | 2 +- .../shadowsocks/bg/TransproxyService.scala | 97 ++++++++ .../VpnService.scala} | 140 +++-------- .../VpnThread.scala} | 9 +- .../shadowsocks/database/DBHelper.scala | 9 +- .../github/shadowsocks/database/Profile.scala | 6 - .../shadowsocks/plugin/PluginManager.scala | 3 +- .../shadowsocks/plugin/ResolvedPlugin.scala | 3 +- .../OrmLitePreferenceDataStore.scala | 6 +- .../github/shadowsocks/utils/Constants.scala | 51 +--- .../com/github/shadowsocks/utils/Utils.scala | 13 +- 35 files changed, 618 insertions(+), 700 deletions(-) delete mode 100644 mobile/src/main/res/drawable/ic_navigation_refresh.xml delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala rename mobile/src/main/scala/com/github/shadowsocks/{ => bg}/BaseService.scala (68%) create mode 100644 mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/bg/LocalDnsService.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/bg/ProxyService.scala rename mobile/src/main/scala/com/github/shadowsocks/{ShadowsocksNotification.scala => bg/ServiceNotification.scala} (93%) create mode 100644 mobile/src/main/scala/com/github/shadowsocks/bg/ServiceState.scala rename mobile/src/main/scala/com/github/shadowsocks/{ShadowsocksTileService.scala => bg/ServiceTileService.scala} (89%) rename mobile/src/main/scala/com/github/shadowsocks/{utils => bg}/TrafficMonitor.scala (98%) rename mobile/src/main/scala/com/github/shadowsocks/{utils => bg}/TrafficMonitorThread.scala (99%) create mode 100644 mobile/src/main/scala/com/github/shadowsocks/bg/TransproxyService.scala rename mobile/src/main/scala/com/github/shadowsocks/{ShadowsocksVpnService.scala => bg/VpnService.scala} (65%) rename mobile/src/main/scala/com/github/shadowsocks/{ShadowsocksVpnThread.scala => bg/VpnThread.scala} (95%) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index fbbe3bff59..0c6884f262 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -109,13 +109,7 @@ - - - - + + + + + + diff --git a/mobile/src/main/res/drawable/ic_navigation_refresh.xml b/mobile/src/main/res/drawable/ic_navigation_refresh.xml deleted file mode 100644 index 8229a9a64c..0000000000 --- a/mobile/src/main/res/drawable/ic_navigation_refresh.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index 70a4596d6f..bc68c7e613 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -226,4 +226,15 @@ @string/acl_rule_templates_domain URL + + + @string/service_mode_proxy + @string/service_mode_vpn + @string/service_mode_transproxy + + + proxy + vpn + transproxy + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 09e252b8bb..1754f6a8f9 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -7,8 +7,15 @@ Profile Switch to another profile or add new profiles - NAT mode (deprecated) - Use NAT mode instead of VPN mode. Requires ROOT permission. + Advanced + Service mode + Proxy only + VPN + Transproxy + SOCKS5 proxy port + Local DNS port + Transproxy port + Remote DNS Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s %1$s↑\t%2$s↓ @@ -42,9 +49,9 @@ Bypass LAN & mainland China GFW List China List - Per-App Proxy - Set proxy for selected apps, requires NAT mode under Android 4.x - Set proxy for selected apps + Apps VPN mode + Allow selected apps to bypass VPN, not available on Android 4.x + Allow selected apps to bypass VPN On Bypass Mode Enable this option to bypass selected apps @@ -57,13 +64,11 @@ VPN Service - NAT Service + Proxy Service + Transproxy Service Shadowsocks started. Invalid server name Failed to connect the remote server - WARNING: NAT mode has been deprecated since Android 5.0 - NAT mode requires ROOT permission - Switch to VPN mode Stop Shutting down… %s @@ -75,7 +80,6 @@ Please select a profile Proxy/Password should not be empty Connect - Resetting… Remove this profile "%s"? @@ -83,7 +87,6 @@ Settings FAQ https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md - Reset About Shadowsocks %s Edit diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index 7d54d22559..44969d5dd6 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -1,5 +1,6 @@ - + - + + + + + + diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 37d8868614..b8a1fc47be 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -20,12 +20,6 @@ android:key="remotePortNum" android:summary="%d" android:title="@string/remote_port"/> - (false, false) + case Key.modeVpn => (true, false) + case Key.modeTransproxy => (true, true) + } + portLocalDns.setEnabled(enabledLocalDns) + portTransproxy.setEnabled(enabledTransproxy) + true + } + MainActivity.stateListener = { + case ServiceState.IDLE | ServiceState.STOPPED => + serviceMode.setEnabled(true) + portProxy.setEnabled(true) + onServiceModeChange(null, app.dataStore.serviceMode) + case _ => + serviceMode.setEnabled(false) + portProxy.setEnabled(false) + portLocalDns.setEnabled(false) + portTransproxy.setEnabled(false) + } + MainActivity.stateListener(getActivity.asInstanceOf[MainActivity].state) + serviceMode.setOnPreferenceChangeListener(onServiceModeChange) } override def onDestroy() { - app.dataStore.unregisterChangeListener(this) + MainActivity.stateListener = null super.onDestroy() } - - def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match { - case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(store.getBoolean(key, false)) - case _ => - } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 9450aac633..145ea7c7e5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -24,12 +24,12 @@ import java.lang.System.currentTimeMillis import java.net.{HttpURLConnection, URL} import java.util.Locale +import android.app.Activity import android.app.backup.BackupManager -import android.app.{Activity, ProgressDialog} import android.content._ import android.net.{Uri, VpnService} import android.nfc.{NdefMessage, NfcAdapter} -import android.os.{Build, Bundle, Handler, Message} +import android.os.{Bundle, Handler} import android.support.customtabs.CustomTabsIntent import android.support.design.widget.{FloatingActionButton, Snackbar} import android.support.v4.content.ContextCompat @@ -45,6 +45,7 @@ import com.github.jorgecastilloprz.FABProgressCircle import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.acl.{Acl, CustomRulesFragment} import com.github.shadowsocks.aidl.IShadowsocksServiceCallback +import com.github.shadowsocks.bg.{Executable, ServiceState, TrafficMonitor} import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.utils.CloseUtils.autoDisconnect import com.github.shadowsocks.utils._ @@ -61,10 +62,11 @@ object MainActivity { private final val DRAWER_PROFILES = 0L private final val DRAWER_GLOBAL_SETTINGS = 1L - private final val DRAWER_RECOVERY = 2L private final val DRAWER_ABOUT = 3L private final val DRAWER_FAQ = 4L private final val DRAWER_CUSTOM_RULES = 5L + + var stateListener: Int => Unit = _ } class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener @@ -109,42 +111,41 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } private def changeState(s: Int, profileName: String = null, m: String = null) { s match { - case State.CONNECTING => + case ServiceState.CONNECTING => fab.setImageResource(R.drawable.ic_start_busy) fabProgressCircle.show() statusText.setText(R.string.connecting) - case State.CONNECTED => - if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() + case ServiceState.CONNECTED => + if (state == ServiceState.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.postDelayed(hideCircle, 1000) fab.setImageResource(R.drawable.ic_start_connected) - statusText.setText(if (app.isNatEnabled) R.string.nat_connected else R.string.vpn_connected) - case State.STOPPING => + statusText.setText(if (app.usingVpnMode) R.string.vpn_connected else R.string.nat_connected) + case ServiceState.STOPPING => fab.setImageResource(R.drawable.ic_start_busy) - if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped + if (state == ServiceState.CONNECTED) fabProgressCircle.show() // ignore for stopped statusText.setText(R.string.stopping) case _ => fab.setImageResource(R.drawable.ic_start_idle) fabProgressCircle.postDelayed(hideCircle, 1000) if (m != null) { - val snackbar = Snackbar.make(findViewById(R.id.snackbar), - getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG) - if (m == getString(R.string.nat_no_root)) addDisableNatToSnackbar(snackbar) - snackbar.show() + Snackbar.make(findViewById(R.id.snackbar), + getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show() Log.e(TAG, "Error to start VPN service: " + m) } statusText.setText(R.string.not_connected) } state = s - if (state == State.CONNECTED) fab.setBackgroundTintList(greenTint) else { + if (state == ServiceState.CONNECTED) fab.setBackgroundTintList(greenTint) else { fab.setBackgroundTintList(greyTint) updateTraffic(-1, 0, 0, 0, 0) testCount += 1 // suppress previous test messages } if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.notifyDataSetChanged() // refresh button enabled state + if (stateListener != null) stateListener(s) fab.setEnabled(false) - if (state == State.CONNECTED || state == State.STOPPED) - handler.postDelayed(() => fab.setEnabled(state == State.CONNECTED || state == State.STOPPED), 1000) + if (state == ServiceState.CONNECTED || state == ServiceState.STOPPED) + handler.postDelayed(() => fab.setEnabled(state == ServiceState.CONNECTED || state == ServiceState.STOPPED), 1000) } def updateTraffic(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { txText.setText(TrafficMonitor.formatTraffic(txTotal)) @@ -152,25 +153,15 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe txRateText.setText(TrafficMonitor.formatTraffic(txRate) + "/s") rxRateText.setText(TrafficMonitor.formatTraffic(rxRate) + "/s") val child = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment] - if (state != State.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal) + if (state != ServiceState.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal) } - override def onServiceConnected() { - changeState(bgService.getState) - if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) { - val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG) - addDisableNatToSnackbar(snackbar) - snackbar.show() - } - } - override def onServiceDisconnected(): Unit = changeState(State.IDLE) - - private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => - if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener) + override def onServiceConnected(): Unit = changeState(bgService.getState) + override def onServiceDisconnected(): Unit = changeState(ServiceState.IDLE) override def binderDied(): Unit = handler.post(() => { detachService() - app.crashRecovery() + Executable.killAll() attachService(callback) }) @@ -210,12 +201,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe .withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_action_help_outline)) .withIconTintingEnabled(true) .withSelectable(false), - new PrimaryDrawerItem() - .withIdentifier(DRAWER_RECOVERY) - .withName(R.string.recovery) - .withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_navigation_refresh)) - .withIconTintingEnabled(true) - .withSelectable(false), new PrimaryDrawerItem() .withIdentifier(DRAWER_ABOUT) .withName(R.string.about) @@ -257,7 +242,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] rxText = findViewById(R.id.rx).asInstanceOf[TextView] rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView] - findViewById[View](R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) { + findViewById[View](R.id.stat).setOnClickListener(_ => if (state == ServiceState.CONNECTED && app.usingVpnMode) { testCount += 1 statusText.setText(R.string.connection_test_testing) val id = testCount // it would change by other code @@ -299,20 +284,20 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] - fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture { - if (app.isNatEnabled) Utils.startSsService(this) else { + fab.setOnClickListener(_ => if (state == ServiceState.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture { + if (app.usingVpnMode) { val intent = VpnService.prepare(this) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)) - } + } else Utils.startSsService(this) }) fab.setOnLongClickListener(_ => { - Utils.positionToast(Toast.makeText(this, if (state == State.CONNECTED) R.string.stop else R.string.connect, + Utils.positionToast(Toast.makeText(this, if (state == ServiceState.CONNECTED) R.string.stop else R.string.connect, Toast.LENGTH_SHORT), fab, getWindow, 0, getResources.getDimensionPixelOffset(R.dimen.margin_small)).show() true }) - changeState(State.IDLE) // reset everything to init state + changeState(ServiceState.IDLE) // reset everything to init state handler.post(() => attachService(callback)) app.dataStore.registerChangeListener(this) @@ -352,7 +337,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match { - case Key.isNAT => handler.post(() => { + case Key.serviceMode => handler.post(() => { detachService() attachService(callback) }) @@ -367,18 +352,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = { drawerItem.getIdentifier match { case DRAWER_PROFILES => displayFragment(new ProfilesFragment) - case DRAWER_RECOVERY => - app.track("GlobalConfigFragment", "reset") - Utils.stopSsService(this) - val dialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, false) - val handler = new Handler { - override def handleMessage(msg: Message): Unit = if (dialog.isShowing && !isDestroyed) dialog.dismiss() - } - Utils.ThrowableFuture { - app.crashRecovery() - app.copyAssets() - handler.sendEmptyMessage(0) - } case DRAWER_GLOBAL_SETTINGS => displayFragment(new GlobalSettingsFragment) case DRAWER_ABOUT => app.track(TAG, "about") @@ -394,7 +367,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe super.onResume() app.remoteConfig.fetch() state match { - case State.STOPPING | State.CONNECTING => + case ServiceState.STOPPING | ServiceState.CONNECTING => case _ => hideCircle() } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 16cdf902a0..7675a68d37 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe findPreference(Key.password).setSummary("\u2022" * 32) } isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled) + isProxyApps.setEnabled(Utils.isLollipopOrAbove && app.usingVpnMode) isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(getActivity, classOf[AppManager])) isProxyApps.setChecked(true) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 796f27a9d1..f3efa8dae8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -31,6 +31,7 @@ import android.view.View.OnLongClickListener import android.view._ import android.widget.{LinearLayout, PopupMenu, TextView, Toast} import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.bg.{ServiceState, TrafficMonitor} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.utils._ @@ -56,12 +57,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic * Is ProfilesFragment editable at all. */ private def isEnabled = getActivity.asInstanceOf[MainActivity].state match { - case State.CONNECTED | State.STOPPED => true + case ServiceState.CONNECTED | ServiceState.STOPPED => true case _ => false } private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match { - case State.CONNECTED => id != app.dataStore.profileId - case State.STOPPED => true + case ServiceState.CONNECTED => id != app.dataStore.profileId + case ServiceState.STOPPED => true case _ => false } @@ -154,7 +155,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic app.switchProfile(item.id) profilesAdapter.refreshId(old) itemView.setSelected(true) - if (activity.state == State.CONNECTED) Utils.reloadSsService(activity) + if (activity.state == ServiceState.CONNECTED) Utils.reloadSsService(activity) } override def onMenuItemClick(menu: MenuItem): Boolean = menu.getItemId match { diff --git a/mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala b/mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala index eadc041afa..29532bb42c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala @@ -26,7 +26,8 @@ import android.content.pm.ShortcutManager import android.os.{Build, Bundle} import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat} import android.support.v4.graphics.drawable.IconCompat -import com.github.shadowsocks.utils.{State, Utils} +import com.github.shadowsocks.bg.ServiceState +import com.github.shadowsocks.utils.Utils /** * @author Mygod @@ -56,8 +57,8 @@ class QuickToggleShortcut extends Activity with ServiceBoundContext { override def onServiceConnected() { bgService.getState match { - case State.STOPPED => Utils.startSsService(this) - case State.CONNECTED => Utils.stopSsService(this) + case ServiceState.STOPPED => Utils.startSsService(this) + case ServiceState.CONNECTED => Utils.stopSsService(this) case _ => // ignore } finish() diff --git a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index f588ff2c7c..c08ef9eb91 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -21,10 +21,11 @@ package com.github.shadowsocks import android.content.{ComponentName, Context, Intent, ServiceConnection} -import android.os.{RemoteException, IBinder} -import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService} +import android.os.{IBinder, RemoteException} +import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.utils.Action import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.bg.{TransproxyService, VpnService} /** * @author Mygod @@ -85,9 +86,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) { this.callback = callback if (bgService == null) { - val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] - - val intent = new Intent(this, s) + val intent = new Intent(this, app.serviceClass) intent.setAction(Action.SERVICE) connection = new ShadowsocksServiceConnection() diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index d8419e0ca1..1f147de76c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -26,12 +26,14 @@ import java.util.Locale import android.annotation.SuppressLint import android.app.{Application, NotificationChannel, NotificationManager} import android.content._ +import android.content.pm.{PackageInfo, PackageManager} import android.content.res.Configuration -import android.os.{Build, LocaleList} +import android.os.{Binder, Build, LocaleList} import android.support.v7.app.AppCompatDelegate import android.util.Log import com.evernote.android.job.JobManager import com.github.shadowsocks.acl.DonaldTrump +import com.github.shadowsocks.bg.{BaseService, ProxyService, TransproxyService, VpnService} import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager} import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.utils.CloseUtils._ @@ -40,10 +42,8 @@ import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardE import com.google.firebase.FirebaseApp import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.j256.ormlite.logger.LocalLog -import eu.chainfire.libsuperuser.Shell import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer object ShadowsocksApplication { var app: ShadowsocksApplication = _ @@ -66,8 +66,12 @@ class ShadowsocksApplication extends Application { lazy val profileManager = new ProfileManager(dbHelper) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper) - def isNatEnabled: Boolean = dataStore.isNAT - def isVpnEnabled: Boolean = !isNatEnabled + def usingVpnMode: Boolean = dataStore.serviceMode == Key.modeVpn + def serviceClass: Class[_] = app.dataStore.serviceMode match { + case Key.modeProxy => classOf[ProxyService] + case Key.modeVpn => classOf[VpnService] + case Key.modeTransproxy => classOf[TransproxyService] + } // send event def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder() @@ -154,32 +158,32 @@ class ShadowsocksApplication extends Application { TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) - if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( - new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), - new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW) - )) - } - - def crashRecovery() { - val cmd = new ArrayBuffer[String]() - - for (task <- Executable.EXECUTABLES) { - cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) - cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" - .formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) + if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets() + // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat + if (!(1025 to 65535 contains dataStore.portProxy)) + dataStore.putInt(Key.portProxy, 1080 + Binder.getCallingUserHandle.hashCode) + if (!(1025 to 65535 contains dataStore.portLocalDns)) + dataStore.putInt(Key.portLocalDns, 5400 + Binder.getCallingUserHandle.hashCode) + if (!(1025 to 65535 contains dataStore.portTransproxy)) + dataStore.putInt(Key.portTransproxy, 8200 + Binder.getCallingUserHandle.hashCode) + + if (Build.VERSION.SDK_INT >= 26) { + val nm = getSystemService(classOf[NotificationManager]) + nm.createNotificationChannels(List( + new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), + new NotificationChannel("service-proxy", getText(R.string.service_proxy), NotificationManager.IMPORTANCE_LOW), + new NotificationChannel("service-transproxy", getText(R.string.service_transproxy), + NotificationManager.IMPORTANCE_LOW) + )) + nm.deleteNotificationChannel("service-nat") // NAT mode is gone for good } - if (app.isNatEnabled) { - cmd.append("iptables -t nat -F OUTPUT") - cmd.append("echo done") - val result = Shell.SU.run(cmd.toArray) - if (result != null && !result.isEmpty) return // fallback to SH - } - Shell.SH.run(cmd.toArray) } + lazy val info: PackageInfo = getPackageManager.getPackageInfo(getPackageName, PackageManager.GET_SIGNATURES) + def copyAssets() { val assetManager = getAssets - for (dir <- List("acl", "overture")) { + for (dir <- Array("acl", "overture")) { var files: Array[String] = null try files = assetManager.list(dir) catch { case e: IOException => @@ -190,11 +194,9 @@ class ShadowsocksApplication extends Application { autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => IOUtils.copy(in, out))) } - dataStore.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE) + dataStore.putLong(Key.assetUpdateTime, info.lastUpdateTime) } - def updateAssets(): Unit = if (dataStore.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets() - def listenForPackageChanges(callback: => Unit): BroadcastReceiver = { val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) filter.addAction(Intent.ACTION_PACKAGE_REMOVED) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala deleted file mode 100644 index dd915eb39c..0000000000 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ /dev/null @@ -1,231 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package com.github.shadowsocks - -import java.io.File -import java.net.{Inet6Address, InetAddress} -import java.util.Locale - -import android.app.Service -import android.content._ -import android.os._ -import android.util.Log -import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob} -import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.utils._ -import eu.chainfire.libsuperuser.Shell - -import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer - -class ShadowsocksNatService extends BaseService { - - val TAG = "ShadowsocksNatService" - - val CMD_IPTABLES_DNAT_ADD_SOCKS = - "iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123" - - val myUid: Int = android.os.Process.myUid() - - var sslocalProcess: GuardedProcess = _ - var sstunnelProcess: GuardedProcess = _ - var redsocksProcess: GuardedProcess = _ - var overtureProcess: GuardedProcess = _ - var su: Shell.Interactive = _ - - def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", - "-b", "127.0.0.1", - "-l", profile.localPort.toString, - "-t", "600", - "-c", buildShadowsocksConfig("ss-local-nat.conf")) - - if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - - if (profile.route != Acl.ALL) { - cmd += "--acl" - cmd += Acl.getFile(profile.route match { - case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED - case route => route - }).getAbsolutePath - } - - sslocalProcess = new GuardedProcess(cmd: _*).start() - } - - def startDNSTunnel() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", - "-t", "10", - "-b", "127.0.0.1", - "-l", (profile.localPort + 63).toString, - "-L", profile.remoteDns.split(",").head.trim + ":53", - "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) - - if (profile.udpdns) cmd.append("-u") - - sstunnelProcess = new GuardedProcess(cmd: _*).start() - } - - def startDnsDaemon() { - overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", - "-c", buildOvertureConfig("overture-nat.conf")) - .start() - } - - def startRedsocksDaemon() { - IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"), - ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)) - redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", - "-c", "redsocks-nat.conf").start() - } - - /** Called when the activity is first created. */ - def handleConnection() { - - startRedsocksDaemon() - startShadowsocksDaemon() - - if (!profile.udpdns) - startDnsDaemon() - else - startDNSTunnel() - - setupIptables() - - } - - def onBind(intent: Intent): IBinder = { - Log.d(TAG, "onBind") - if (Action.SERVICE == intent.getAction) { - binder - } else { - null - } - } - - def killProcesses() { - if (sslocalProcess != null) { - sslocalProcess.destroy() - sslocalProcess = null - } - if (sstunnelProcess != null) { - sstunnelProcess.destroy() - sstunnelProcess = null - } - if (redsocksProcess != null) { - redsocksProcess.destroy() - redsocksProcess = null - } - if (overtureProcess != null) { - overtureProcess.destroy() - overtureProcess = null - } - - su.addCommand("iptables -t nat -F OUTPUT") - } - - def setupIptables() { - val init_sb = new ArrayBuffer[String] - val http_sb = new ArrayBuffer[String] - - init_sb.append("ulimit -n 4096") - init_sb.append("iptables -t nat -F OUTPUT") - - val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" - if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) - } - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1")) - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) - init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53")) - - init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" - + (profile.localPort + 53)) - - if (!profile.proxyApps || profile.bypass) { - http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS) - } - if (profile.proxyApps) { - val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap - for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { - case Some(uid) => - if (!profile.bypass) { - http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS - .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) - } else { - init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid)) - } - case _ => // probably removed package, ignore - } - } - su.addCommand((init_sb ++ http_sb).toArray) - } - - override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) { - su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) => - if (exitCode == 0) super.onStartCommand(intent, flags, startId) else { - if (su != null) { - su.close() - su = null - } - super.stopRunner(stopService = true, getString(R.string.nat_no_root)) - }) - Service.START_NOT_STICKY - } else super.onStartCommand(intent, flags, startId) - - override def connect() { - super.connect() - - // Clean up - killProcesses() - - if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { - case Some(a) => profile.host = a - case None => throw NameNotResolvedException() - } - - handleConnection() - - if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) - AclSyncJob.schedule(profile.route) - - changeState(State.CONNECTED) - } - - override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true) - - override def stopRunner(stopService: Boolean, msg: String = null) { - - // channge the state - changeState(State.STOPPING) - - app.track(TAG, "stop") - - // reset NAT - killProcesses() - - su.close() - su = null - - super.stopRunner(stopService, msg) - } -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index 9fd86e8e97..d37d4d9ba8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -46,16 +46,12 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { } def startBackgroundService() { - if (app.isNatEnabled) { + if (app.usingVpnMode) VpnService.prepare(ShadowsocksRunnerActivity.this) match { + case null => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) + case intent => startActivityForResult(intent, REQUEST_CONNECT) + } else { Utils.startSsService(this) finish() - } else { - val intent = VpnService.prepare(ShadowsocksRunnerActivity.this) - if (intent != null) { - startActivityForResult(intent, REQUEST_CONNECT) - } else { - onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) - } } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala index 6e4a2c790b..85399e7497 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala @@ -15,7 +15,7 @@ import android.view._ import android.widget.{EditText, Spinner, TextView, Toast} import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider} import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.utils.State +import com.github.shadowsocks.bg.ServiceState import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.{MainActivity, R, ToolbarFragment} @@ -41,8 +41,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi import CustomRulesFragment._ private def isEnabled = getActivity.asInstanceOf[MainActivity].state match { - case State.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES - case State.STOPPED => true + case ServiceState.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES + case ServiceState.STOPPED => true case _ => false } diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/BaseService.scala similarity index 68% rename from mobile/src/main/scala/com/github/shadowsocks/BaseService.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/BaseService.scala index 19a2e8aac1..11484b6d4c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/BaseService.scala @@ -18,10 +18,10 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks +package com.github.shadowsocks.bg import java.io.{File, IOException} -import java.net.{Inet6Address, InetAddress} +import java.net.InetAddress import java.util import java.util.concurrent.TimeUnit import java.util.{Timer, TimerTask} @@ -33,25 +33,27 @@ import android.text.TextUtils import android.util.Log import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.Acl +import com.github.shadowsocks.acl.{Acl, AclSyncJob} import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions} import com.github.shadowsocks.utils._ +import com.github.shadowsocks.{GuardedProcess, R} import okhttp3.{Dns, FormBody, OkHttpClient, Request} -import org.json.{JSONArray, JSONObject} +import org.json.JSONObject -import scala.collection.JavaConversions._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.util.Random trait BaseService extends Service { - @volatile private var state = State.STOPPED + protected val TAG: String + @volatile private var state = ServiceState.STOPPED @volatile protected var profile: Profile = _ @volatile private var plugin: PluginOptions = _ @volatile protected var pluginPath: String = _ + var sslocalProcess: GuardedProcess = _ case class NameNotResolvedException() extends IOException case class NullConnectionException() extends NullPointerException @@ -64,7 +66,7 @@ trait BaseService extends Service { lazy val handler = new Handler(getMainLooper) lazy val restartHanlder = new Handler(getMainLooper) - private var notification: ShadowsocksNotification = _ + private var notification: ServiceNotification = _ private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => intent.getAction match { case Action.RELOAD => forceLoad() case _ => @@ -87,11 +89,11 @@ trait BaseService extends Service { if (timer == null) { timer = new Timer(true) timer.schedule(new TimerTask { - def run(): Unit = if (state == State.CONNECTED && TrafficMonitor.updateRate()) updateTrafficRate() + def run(): Unit = if (state == ServiceState.CONNECTED && TrafficMonitor.updateRate()) updateTrafficRate() }, 1000, 1000) } TrafficMonitor.updateRate() - if (state == State.CONNECTED) cb.trafficUpdated(profile.id, + if (state == ServiceState.CONNECTED) cb.trafficUpdated(profile.id, TrafficMonitor.txRate, TrafficMonitor.rxRate, TrafficMonitor.txTotal, TrafficMonitor.rxTotal) } @@ -107,6 +109,11 @@ trait BaseService extends Service { } } + def onBind(intent: Intent): IBinder = intent.getAction match { + case Action.SERVICE => binder + case _ => null + } + def checkProfile(profile: Profile): Boolean = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) { stopRunner(stopService = true, getString(R.string.proxy_empty)) false @@ -115,52 +122,61 @@ trait BaseService extends Service { def forceLoad(): Unit = app.currentProfile.orNull match { case null => stopRunner(stopService = true, getString(R.string.profile_empty)) case p => if (checkProfile(p)) state match { - case State.STOPPED => startRunner() - case State.CONNECTED => + case ServiceState.STOPPED => startRunner() + case ServiceState.CONNECTED => stopRunner(stopService = false) startRunner() case s => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + s) } } - def connect() { - if (profile.host == "198.199.101.152") { - val client = new OkHttpClient.Builder() - .dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match { - case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip)) - case _ => Dns.SYSTEM.lookup(hostname) - }) - .connectTimeout(10, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() - val requestBody = new FormBody.Builder() - .add("sig", Utils.getSignature(this)) - .build() - val request = new Request.Builder() - .url(app.remoteConfig.getString("proxy_url")) - .post(requestBody) - .build() - - val proxies = Random.shuffle(client.newCall(request).execute().body.string.split('|').toSeq) - val proxy = proxies.head.split(':') - profile.host = proxy(0).trim - profile.remotePort = proxy(1).trim.toInt - profile.password = proxy(2).trim - profile.method = proxy(3).trim + protected def buildAdditionalArguments(cmd: ArrayBuffer[String]): ArrayBuffer[String] = cmd + + /** + * BaseService will only start ss-local. Child class override this class to start other native processes. + */ + def startNativeProcesses() { + buildShadowsocksConfig() + val cmd = buildAdditionalArguments(ArrayBuffer[String]( + new File(getApplicationInfo.nativeLibraryDir, Executable.SS_LOCAL).getAbsolutePath, + "-u", + "-b", "127.0.0.1", + "-l", app.dataStore.portProxy.toString, + "-t", "600", + "-c", "shadowsocks.json")) + + if (profile.route != Acl.ALL) { + cmd += "--acl" + cmd += Acl.getFile(profile.route match { + case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED + case route => route + }).getAbsolutePath } - if (profile.route == Acl.CUSTOM_RULES) Acl.save(Acl.CUSTOM_RULES_FLATTENED, Acl.customRules.flatten(10)) + if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - plugin = new PluginConfiguration(profile.plugin).selectedOptions - pluginPath = PluginManager.init(plugin) + sslocalProcess = new GuardedProcess(cmd: _*).start() } - def createNotification(): ShadowsocksNotification + def createNotification(): ServiceNotification def startRunner(): Unit = if (Build.VERSION.SDK_INT >= 26) startForegroundService(new Intent(this, getClass)) else startService(new Intent(this, getClass)) + def killProcesses() { + if (sslocalProcess != null) { + sslocalProcess.destroy() + sslocalProcess = null + } + } + def stopRunner(stopService: Boolean, msg: String = null) { + // channge the state + changeState(ServiceState.STOPPING) + + app.track(TAG, "stop") + + killProcesses() + // clean up recevier if (closeReceiverRegistered) { unregisterReceiver(closeReceiver) @@ -179,7 +195,7 @@ trait BaseService extends Service { } // change the state - changeState(State.STOPPED, msg) + changeState(ServiceState.STOPPED, msg) // stop the service if nothing has bound to it if (stopService) stopSelf() @@ -240,16 +256,10 @@ trait BaseService extends Service { }) } - - override def onCreate() { - super.onCreate() - app.updateAssets() - } - // Service of shadowsocks should always be started explicitly override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = { state match { - case State.STOPPED | State.IDLE => + case ServiceState.STOPPED | ServiceState.IDLE => case _ => return Service.START_NOT_STICKY // ignore request } @@ -278,9 +288,55 @@ trait BaseService extends Service { notification = createNotification() app.track(getClass.getSimpleName, "start") - changeState(State.CONNECTING) + changeState(ServiceState.CONNECTING) + + Utils.ThrowableFuture(try { + if (profile.host == "198.199.101.152") { + val client = new OkHttpClient.Builder() + .dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match { + case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip)) + case _ => Dns.SYSTEM.lookup(hostname) + }) + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + val requestBody = new FormBody.Builder() + .add("sig", Utils.getSignature(this)) + .build() + val request = new Request.Builder() + .url(app.remoteConfig.getString("proxy_url")) + .post(requestBody) + .build() + + val proxies = Random.shuffle(client.newCall(request).execute().body.string.split('|').toSeq) + val proxy = proxies.head.split(':') + profile.host = proxy(0).trim + profile.remotePort = proxy(1).trim.toInt + profile.password = proxy(2).trim + profile.method = proxy(3).trim + } + + if (profile.route == Acl.CUSTOM_RULES) Acl.save(Acl.CUSTOM_RULES_FLATTENED, Acl.customRules.flatten(10)) + + plugin = new PluginConfiguration(profile.plugin).selectedOptions + pluginPath = PluginManager.init(plugin) - Utils.ThrowableFuture(try connect() catch { + // Clean up + killProcesses() + + if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { + case Some(a) => profile.host = a + case None => throw NameNotResolvedException() + } + + startNativeProcesses() + + if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) + AclSyncJob.schedule(profile.route) + + changeState(ServiceState.CONNECTED) + } catch { case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server)) case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required)) case exc: Throwable => @@ -309,78 +365,19 @@ trait BaseService extends Service { } } - protected def buildPluginCommandLine(): ArrayBuffer[String] = { - val result = ArrayBuffer(pluginPath) - if (TcpFastOpen.sendEnabled) result += "--fast-open" - result - } - - protected final def buildShadowsocksConfig(file: String): String = { + protected final def buildShadowsocksConfig() { val config = new JSONObject() .put("server", profile.host) .put("server_port", profile.remotePort) .put("password", profile.password) .put("method", profile.method) - if (pluginPath != null) config - .put("plugin", Commandline.toString(buildPluginCommandLine())) - .put("plugin_opts", plugin.toString) - IOUtils.writeString(new File(getFilesDir, file), config.toString) - file - } - - protected final def buildOvertureConfig(file: String): String = { - val config = new JSONObject() - .put("BindAddress", "127.0.0.1:" + (profile.localPort + 53)) - .put("RedirectIPv6Record", true) - .put("DomainBase64Decode", true) - .put("HostsFile", "hosts") - .put("MinimumTTL", 3600) - .put("CacheSize", 4096) - def makeDns(name: String, address: String, edns: Boolean = true) = { - val dns = new JSONObject() - .put("Name", name) - .put("Address", (Utils.parseNumericAddress(address) match { - case _: Inet6Address => '[' + address + ']' - case _ => address - }) + ":53") - .put("Timeout", 6) - .put("EDNSClientSubnet", new JSONObject().put("Policy", "disable")) - if (edns) dns - .put("Protocol", "tcp") - .put("Socks5Address", "127.0.0.1:" + profile.localPort) - else dns.put("Protocol", "udp") - dns - } - val remoteDns = new JSONArray(profile.remoteDns.split(",").zipWithIndex.map { - case (dns, i) => makeDns("UserDef-" + i, dns.trim) - }) - val localDns = new JSONArray(Array( - makeDns("Primary-1", "119.29.29.29", edns = false), - makeDns("Primary-2", "114.114.114.114", edns = false) - )) - - try { - val localLinkDns = com.github.shadowsocks.utils.Dns.getDnsResolver(this) - localDns.put(makeDns("Primary-3", localLinkDns, edns = false)) - } catch { - case _: Exception => // Ignore - } - - profile.route match { - case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config - .put("PrimaryDNS", localDns) - .put("AlternativeDNS", remoteDns) - .put("IPNetworkFile", "china_ip_list.txt") - .put("DomainFile", "gfwlist.txt") - case Acl.CHINALIST => config - .put("PrimaryDNS", localDns) - .put("AlternativeDNS", remoteDns) - case _ => config - .put("PrimaryDNS", remoteDns) - // no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode - .put("OnlyPrimaryDNS", true) + if (pluginPath != null) { + val pluginCmd = ArrayBuffer(pluginPath) + if (TcpFastOpen.sendEnabled) pluginCmd += "--fast-open" + config + .put("plugin", Commandline.toString(buildAdditionalArguments(pluginCmd).toArray)) + .put("plugin_opts", plugin.toString) } - IOUtils.writeString(new File(getFilesDir, file), config.toString) - file + IOUtils.writeString(new File(getFilesDir, "shadowsocks.json"), config.toString) } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala new file mode 100644 index 0000000000..7d2a1e3832 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala @@ -0,0 +1,27 @@ +package com.github.shadowsocks.bg + +import java.util.Locale +import java.util.concurrent.TimeUnit + +import android.util.Log + +/** + * @author Mygod + */ +object Executable { + val REDSOCKS = "libredsocks.so" + val PDNSD = "libpdnsd.so" + val SS_LOCAL = "libss-local.so" + val SS_TUNNEL = "libss-tunnel.so" + val TUN2SOCKS = "libtun2socks.so" + val OVERTURE = "liboverture.so" + + val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE) + + def killAll() { + val killer = new ProcessBuilder("killall" +: EXECUTABLES: _*).start() + if (!killer.waitFor(1, TimeUnit.SECONDS)) + Log.w("killall", "%s didn't exit within 1s. Post-crash clean-up may have failed." + .formatLocal(Locale.ENGLISH, killer.toString)) + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/LocalDnsService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/LocalDnsService.scala new file mode 100644 index 0000000000..4e8511b672 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/LocalDnsService.scala @@ -0,0 +1,93 @@ +package com.github.shadowsocks.bg + +import java.io.File +import java.net.Inet6Address + +import com.github.shadowsocks.GuardedProcess +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.acl.Acl +import com.github.shadowsocks.utils.{IOUtils, Utils} +import org.json.{JSONArray, JSONObject} + +import scala.collection.mutable.ArrayBuffer + +/** + * Shadowsocks service with local DNS. + * + * @author Mygod + */ +trait LocalDnsService extends BaseService { + var overtureProcess: GuardedProcess = _ + + override def startNativeProcesses() { + super.startNativeProcesses() + if (!profile.udpdns) overtureProcess = new GuardedProcess(buildAdditionalArguments(ArrayBuffer[String]( + new File(getApplicationInfo.nativeLibraryDir, Executable.OVERTURE).getAbsolutePath, + "-c", buildOvertureConfig("overture.conf") + )): _*).start() + } + + private def buildOvertureConfig(file: String): String = { + val config = new JSONObject() + .put("BindAddress", "127.0.0.1:" + app.dataStore.portLocalDns) + .put("RedirectIPv6Record", true) + .put("DomainBase64Decode", true) + .put("HostsFile", "hosts") + .put("MinimumTTL", 3600) + .put("CacheSize", 4096) + def makeDns(name: String, address: String, edns: Boolean = true) = { + val dns = new JSONObject() + .put("Name", name) + .put("Address", (Utils.parseNumericAddress(address) match { + case _: Inet6Address => '[' + address + ']' + case _ => address + }) + ":53") + .put("Timeout", 6) + .put("EDNSClientSubnet", new JSONObject().put("Policy", "disable")) + if (edns) dns + .put("Protocol", "tcp") + .put("Socks5Address", "127.0.0.1:" + app.dataStore.portProxy) + else dns.put("Protocol", "udp") + dns + } + val remoteDns = new JSONArray(profile.remoteDns.split(",").zipWithIndex.map { + case (dns, i) => makeDns("UserDef-" + i, dns.trim) + }) + val localDns = new JSONArray(Array( + makeDns("Primary-1", "119.29.29.29", edns = false), + makeDns("Primary-2", "114.114.114.114", edns = false) + )) + + try { + val localLinkDns = com.github.shadowsocks.utils.Dns.getDnsResolver(this) + localDns.put(makeDns("Primary-3", localLinkDns, edns = false)) + } catch { + case _: Exception => // Ignore + } + + profile.route match { + case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config + .put("PrimaryDNS", localDns) + .put("AlternativeDNS", remoteDns) + .put("IPNetworkFile", "china_ip_list.txt") + .put("DomainFile", "gfwlist.txt") + case Acl.CHINALIST => config + .put("PrimaryDNS", localDns) + .put("AlternativeDNS", remoteDns) + case _ => config + .put("PrimaryDNS", remoteDns) + // no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode + .put("OnlyPrimaryDNS", true) + } + IOUtils.writeString(new File(getFilesDir, file), config.toString) + file + } + + override def killProcesses() { + super.killProcesses() + if (overtureProcess != null) { + overtureProcess.destroy() + overtureProcess = null + } + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/ProxyService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ProxyService.scala new file mode 100644 index 0000000000..44fdf2c320 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ProxyService.scala @@ -0,0 +1,12 @@ +package com.github.shadowsocks.bg + +/** + * Shadowsocks service at its minimum. + * + * @author Mygod + */ +class ProxyService extends BaseService { + val TAG = "ShadowsocksProxyService" + + def createNotification() = new ServiceNotification(this, profile.name, "service-proxy", true) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala similarity index 93% rename from mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala index 6433d9854a..252e585305 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala @@ -18,7 +18,7 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks +package com.github.shadowsocks.bg import java.util.Locale @@ -28,15 +28,15 @@ import android.os.{Build, PowerManager} import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat.BigTextStyle import android.support.v4.content.ContextCompat -import android.support.v4.os.BuildCompat import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub -import com.github.shadowsocks.utils.{Action, State, TrafficMonitor, Utils} +import com.github.shadowsocks.utils.{Action, Utils} +import com.github.shadowsocks.{MainActivity, R} /** * @author Mygod */ -class ShadowsocksNotification(private val service: BaseService, profileName: String, - channel: String, visible: Boolean = false) { +class ServiceNotification(private val service: BaseService, profileName: String, + channel: String, visible: Boolean = false) { private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager] private lazy val callback = new Stub { @@ -76,7 +76,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str service.registerReceiver(lockReceiver, screenFilter) private def update(action: String, forceShow: Boolean = false) = - if (forceShow || service.getState == State.CONNECTED) action match { + if (forceShow || service.getState == ServiceState.CONNECTED) action match { case Intent.ACTION_SCREEN_OFF => setVisible(visible && !Utils.isLollipopOrAbove, forceShow) unregisterCallback() // unregister callback to save battery diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceState.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceState.scala new file mode 100644 index 0000000000..4b84a79048 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceState.scala @@ -0,0 +1,16 @@ +package com.github.shadowsocks.bg + +/** + * @author Mygod + */ +object ServiceState { + /** + * This state will never be broadcast by the service. This state is only used to indicate that the current context + * hasn't bound to any context. + */ + val IDLE = 0 + val CONNECTING = 1 + val CONNECTED = 2 + val STOPPING = 3 + val STOPPED = 4 +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceTileService.scala similarity index 89% rename from mobile/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/ServiceTileService.scala index c31a8aece7..7b3ee43a1e 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceTileService.scala @@ -18,20 +18,20 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks +package com.github.shadowsocks.bg import android.annotation.TargetApi import android.graphics.drawable.Icon import android.service.quicksettings.{Tile, TileService} import com.github.shadowsocks.aidl.IShadowsocksServiceCallback -import com.github.shadowsocks.utils.{State, Utils} +import com.github.shadowsocks.utils.Utils +import com.github.shadowsocks.{R, ServiceBoundContext} /** * @author Mygod */ @TargetApi(24) -final class ShadowsocksTileService extends TileService with ServiceBoundContext { - +final class ServiceTileService extends TileService with ServiceBoundContext { private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff) private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy) private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected) @@ -41,11 +41,11 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext val tile = getQsTile if (tile != null) { state match { - case State.STOPPED => + case ServiceState.STOPPED => tile.setIcon(iconIdle) tile.setLabel(getString(R.string.app_name)) tile.setState(Tile.STATE_INACTIVE) - case State.CONNECTED => + case ServiceState.CONNECTED => tile.setIcon(iconConnected) tile.setLabel(if (profileName == null) getString(R.string.app_name) else profileName) tile.setState(Tile.STATE_ACTIVE) @@ -74,8 +74,8 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext override def onClick(): Unit = if (isLocked) unlockAndRun(toggle) else toggle() private def toggle() = if (bgService != null) bgService.getState match { - case State.STOPPED => Utils.startSsService(this) - case State.CONNECTED => Utils.stopSsService(this) + case ServiceState.STOPPED => Utils.startSsService(this) + case ServiceState.CONNECTED => Utils.stopSsService(this) case _ => // ignore } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitor.scala similarity index 98% rename from mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitor.scala index 6e4d792708..f3ed96cdb4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitor.scala @@ -18,7 +18,7 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks.utils +package com.github.shadowsocks.bg import java.text.DecimalFormat diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitorThread.scala similarity index 99% rename from mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitorThread.scala index 0edb656fe8..d1d02635a5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/TrafficMonitorThread.scala @@ -18,7 +18,7 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks.utils +package com.github.shadowsocks.bg import java.io.{File, IOException} import java.nio.{ByteBuffer, ByteOrder} diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/TransproxyService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/TransproxyService.scala new file mode 100644 index 0000000000..6e3cea289a --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/TransproxyService.scala @@ -0,0 +1,97 @@ +/*******************************************************************************/ +/* */ +/* Copyright (C) 2017 by Max Lv */ +/* Copyright (C) 2017 by Mygod Studio */ +/* */ +/* This program is free software: you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation, either version 3 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/* */ +/*******************************************************************************/ + +package com.github.shadowsocks.bg + +import java.io.File +import java.util.Locale + +import com.github.shadowsocks.GuardedProcess +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.utils.IOUtils + +import scala.collection.mutable.ArrayBuffer + +object TransproxyService { + private val REDSOCKS_CONFIG = "base {\n" + + " log_debug = off;\n" + + " log_info = off;\n" + + " log = stderr;\n" + + " daemon = off;\n" + + " redirector = iptables;\n" + + "}\n" + + "redsocks {\n" + + " local_ip = 127.0.0.1;\n" + + " local_port = %d;\n" + + " ip = 127.0.0.1;\n" + + " port = %d;\n" + + " type = socks5;\n" + + "}\n" +} + +class TransproxyService extends LocalDnsService { + import TransproxyService._ + + val TAG = "ShadowsocksTransproxyService" + + var sstunnelProcess: GuardedProcess = _ + var redsocksProcess: GuardedProcess = _ + + def startDNSTunnel() { + val cmd = ArrayBuffer[String](new File(getApplicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).getAbsolutePath, + "-t", "10", + "-b", "127.0.0.1", + "-u", + "-l", app.dataStore.portLocalDns.toString, // ss-tunnel listens on the same port as overture + "-L", profile.remoteDns.split(",").head.trim + ":53", + "-c", "shadowsocks.json") // config is already built by BaseService + + sstunnelProcess = new GuardedProcess(cmd: _*).start() + } + + def startRedsocksDaemon() { + IOUtils.writeString(new File(getFilesDir, "redsocks.conf"), + REDSOCKS_CONFIG.formatLocal(Locale.ENGLISH, app.dataStore.portTransproxy, app.dataStore.portProxy)) + redsocksProcess = new GuardedProcess( + new File(getApplicationInfo.nativeLibraryDir, Executable.REDSOCKS).getAbsolutePath, + "-c", "redsocks.conf" + ).start() + } + + override def startNativeProcesses() { + startRedsocksDaemon() + super.startNativeProcesses() + if (profile.udpdns) startDNSTunnel() + } + + override def killProcesses() { + super.killProcesses() + if (sstunnelProcess != null) { + sstunnelProcess.destroy() + sstunnelProcess = null + } + if (redsocksProcess != null) { + redsocksProcess.destroy() + redsocksProcess = null + } + } + + def createNotification() = new ServiceNotification(this, profile.name, "service-transproxy", true) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/VpnService.scala similarity index 65% rename from mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/VpnService.scala index 20a3c6d5ae..797d1eb642 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/VpnService.scala @@ -18,78 +18,49 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks +package com.github.shadowsocks.bg import java.io.File import java.util.Locale import android.app.Service -import android.content._ +import android.content.Intent import android.content.pm.PackageManager.NameNotFoundException -import android.net.VpnService -import android.os._ +import android.net.{VpnService => BaseVpnService} +import android.os.{IBinder, ParcelFileDescriptor} import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet} -import com.github.shadowsocks.utils._ +import com.github.shadowsocks._ +import com.github.shadowsocks.acl.{Acl, Subnet} +import com.github.shadowsocks.utils.Utils import scala.collection.mutable.ArrayBuffer -class ShadowsocksVpnService extends VpnService with BaseService { +class VpnService extends BaseVpnService with LocalDnsService { val TAG = "ShadowsocksVpnService" val VPN_MTU = 1500 val PRIVATE_VLAN = "26.26.26.%s" val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s" var conn: ParcelFileDescriptor = _ - var vpnThread: ShadowsocksVpnThread = _ + var vpnThread: VpnThread = _ - var sslocalProcess: GuardedProcess = _ - var overtureProcess: GuardedProcess = _ var tun2socksProcess: GuardedProcess = _ - override def onBind(intent: Intent): IBinder = { - val action = intent.getAction - if (VpnService.SERVICE_INTERFACE == action) { - return super.onBind(intent) - } else if (Action.SERVICE == action) { - return binder - } - null + override def onBind(intent: Intent): IBinder = intent.getAction match { + case BaseVpnService.SERVICE_INTERFACE => super[VpnService].onBind(intent) + case _ => super[LocalDnsService].onBind(intent) } override def onRevoke() { stopRunner(stopService = true) } - override def stopRunner(stopService: Boolean, msg: String = null) { - + override def killProcesses() { if (vpnThread != null) { vpnThread.stopThread() vpnThread = null } - - // channge the state - changeState(State.STOPPING) - - app.track(TAG, "stop") - - // reset VPN - killProcesses() - - // close connections - if (conn != null) { - conn.close() - conn = null - } - - super.stopRunner(stopService, msg) - } - - def killProcesses() { - if (sslocalProcess != null) { - sslocalProcess.destroy() - sslocalProcess = null - } + super.killProcesses() if (tun2socksProcess != null) { tun2socksProcess.destroy() tun2socksProcess = null @@ -98,85 +69,41 @@ class ShadowsocksVpnService extends VpnService with BaseService { overtureProcess.destroy() overtureProcess = null } + // close connections + if (conn != null) { + conn.close() + conn = null + } } - override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = { + override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (app.usingVpnMode) // ensure the VPNService is prepared - if (VpnService.prepare(this) != null) { + if (BaseVpnService.prepare(this) != null) { val i = new Intent(this, classOf[ShadowsocksRunnerActivity]) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(i) stopRunner(stopService = true) Service.START_NOT_STICKY } else super.onStartCommand(intent, flags, startId) + else { // system or other apps are trying to start this service but other services could be running + stopSelf() + Service.START_NOT_STICKY } - override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-vpn") - - override def connect() { - super.connect() - - vpnThread = new ShadowsocksVpnThread(this) - vpnThread.start() - - // reset the context - killProcesses() - - // Resolve the server address - if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { - case Some(addr) => profile.host = addr - case None => throw NameNotResolvedException() - } - - handleConnection() - changeState(State.CONNECTED) - - if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) - AclSyncJob.schedule(profile.route) - } + override def createNotification() = new ServiceNotification(this, profile.name, "service-vpn") /** Called when the activity is first created. */ - def handleConnection() { - - startShadowsocksDaemon() + override def startNativeProcesses() { + vpnThread = new VpnThread(this) + vpnThread.start() - if (!profile.udpdns) { - startDnsDaemon() - } + super.startNativeProcesses() val fd = startVpn() if (!sendFd(fd)) throw new Exception("sendFd failed") } - override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V" - - def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", - "-V", - "-u", - "-b", "127.0.0.1", - "-l", profile.localPort.toString, - "-t", "600", - "-c", buildShadowsocksConfig("ss-local-vpn.conf")) - - if (profile.route != Acl.ALL) { - cmd += "--acl" - cmd += Acl.getFile(profile.route match { - case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED - case route => route - }).getAbsolutePath - } - - if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - - sslocalProcess = new GuardedProcess(cmd: _*).start() - } - - def startDnsDaemon() { - overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", - "-c", buildOvertureConfig("overture-vpn.conf"), "-V") - .start() - } + override protected def buildAdditionalArguments(cmd: ArrayBuffer[String]): ArrayBuffer[String] = cmd += "-V" def startVpn(): Int = { @@ -229,10 +156,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { val fd = conn.getFd - var cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libtun2socks.so", + var cmd = ArrayBuffer[String](new File(getApplicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).getAbsolutePath, "--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), "--netif-netmask", "255.255.255.0", - "--socks-server-addr", "127.0.0.1:" + profile.localPort, + "--socks-server-addr", "127.0.0.1:" + app.dataStore.portProxy, "--tunfd", fd.toString, "--tunmtu", VPN_MTU.toString, "--sock-path", "sock_path", @@ -244,8 +171,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { cmd += "--enable-udprelay" if (!profile.udpdns) - cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1", - profile.localPort + 53)) + cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1", app.dataStore.portLocalDns)) tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/VpnThread.scala similarity index 95% rename from mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala rename to mobile/src/main/scala/com/github/shadowsocks/bg/VpnThread.scala index 42eddfd77e..337fec6574 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/VpnThread.scala @@ -18,7 +18,7 @@ /* */ /*******************************************************************************/ -package com.github.shadowsocks +package com.github.shadowsocks.bg import java.io.{File, FileDescriptor, IOException} import java.lang.reflect.Method @@ -26,14 +26,15 @@ import java.util.concurrent.Executors import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.util.Log +import com.github.shadowsocks.JniHelper import com.github.shadowsocks.ShadowsocksApplication.app -object ShadowsocksVpnThread { +object VpnThread { val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$") } -class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread { - import ShadowsocksVpnThread._ +class VpnThread(service: VpnService) extends Thread { + import VpnThread._ val TAG = "ShadowsocksVpnService" val protect = new File(service.getFilesDir, "protect_path") diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index eb67e9272d..880394c70f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -22,11 +22,12 @@ package com.github.shadowsocks.database import java.nio.ByteBuffer -import android.content.{Context, SharedPreferences} +import android.content.Context import android.content.pm.ApplicationInfo import android.database.sqlite.SQLiteDatabase import android.preference.PreferenceManager import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.utils.Key import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper import com.j256.ormlite.dao.Dao @@ -50,7 +51,7 @@ object DBHelper { } class DBHelper(val context: Context) - extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 23) { + extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 24) { import DBHelper._ lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) @@ -148,12 +149,8 @@ class DBHelper(val context: Context) val old = PreferenceManager.getDefaultSharedPreferences(app) kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT, ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array())) - kvPairDao.createOrUpdate(new KeyValuePair(Key.isNAT, TYPE_BOOLEAN, - ByteBuffer.allocate(1).put((if (old.getBoolean(Key.isNAT, false)) 1 else 0).toByte).array())) kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN, ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array())) - kvPairDao.createOrUpdate(new KeyValuePair(Key.currentVersionCode, TYPE_INT, - ByteBuffer.allocate(4).putInt(-1).array())) } } catch { case ex: Exception => diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index a656081418..33d15c0772 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -40,10 +40,6 @@ class Profile { @DatabaseField var host: String = "198.199.101.152" - // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat - @DatabaseField - var localPort: Int = 1080 + Binder.getCallingUserHandle.hashCode - @DatabaseField var remotePort: Int = 8388 @@ -111,7 +107,6 @@ class Profile { def serialize(store: OrmLitePreferenceDataStore) { store.putString(Key.name, name) store.putString(Key.host, host) - store.putInt(Key.localPort, localPort) store.putInt(Key.remotePort, remotePort) store.putString(Key.password, password) store.putString(Key.route, route) @@ -129,7 +124,6 @@ class Profile { // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case name = store.getString(Key.name, null) host = store.getString(Key.host, null) - localPort = store.getInt(Key.localPort, 0) remotePort = store.getInt(Key.remotePort, 0) password = store.getString(Key.password, null) method = store.getString(Key.method, null) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 7f8f536c73..b32b62a1ba 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -28,8 +28,7 @@ object PluginManager { * If you don't plan to publish any plugin but is developing/has developed some, it's not necessary to add your * public key yet since it will also automatically trust packages signed by the same signatures, e.g. debug keys. */ - lazy val trustedSignatures: Set[Signature] = - app.getPackageManager.getPackageInfo(app.getPackageName, PackageManager.GET_SIGNATURES).signatures.toSet + + lazy val trustedSignatures: Set[Signature] = app.info.signatures.toSet + new Signature(Base64.decode( // @Mygod """ |MIIDWzCCAkOgAwIBAgIEUzfv8DANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala index ac23b39035..5ef19149b1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala @@ -19,6 +19,5 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) override def packageName: String = resolveInfo.resolvePackageName - override final lazy val trusted: Boolean = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) - .signatures.exists(PluginManager.trustedSignatures.contains) + override final lazy val trusted: Boolean = app.info.signatures.exists(PluginManager.trustedSignatures.contains) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala index 0e48ccaaf1..cb4c051c9b 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala @@ -100,8 +100,10 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat def profileId: Int = getInt(Key.id, 0) def profileId_=(i: Int): Unit = putInt(Key.id, i) - def isNAT: Boolean = getBoolean(Key.isNAT) - def isNAT_=(value: Boolean): Unit = putBoolean(Key.isNAT, value) + def serviceMode: String = getString(Key.serviceMode, Key.modeVpn) + def portProxy: Int = getInt(Key.portProxy, 0) + def portLocalDns: Int = getInt(Key.portLocalDns, 0) + def portTransproxy: Int = getInt(Key.portTransproxy, 0) def proxyApps: Boolean = getBoolean(Key.proxyApps) def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index e7e30a9279..86fd17e249 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -20,40 +20,20 @@ package com.github.shadowsocks.utils -object Executable { - val REDSOCKS = "redsocks" - val PDNSD = "pdnsd" - val SS_LOCAL = "ss-local" - val SS_TUNNEL = "ss-tunnel" - val TUN2SOCKS = "tun2socks" - - val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS) -} - -object ConfigUtils { - val REDSOCKS = "base {\n" + - " log_debug = off;\n" + - " log_info = off;\n" + - " log = stderr;\n" + - " daemon = off;\n" + - " redirector = iptables;\n" + - "}\n" + - "redsocks {\n" + - " local_ip = 127.0.0.1;\n" + - " local_port = 8123;\n" + - " ip = 127.0.0.1;\n" + - " port = %d;\n" + - " type = socks5;\n" + - "}\n" -} - object Key { val id = "profileId" val name = "profileName" val individual = "Proxyed" - val isNAT = "isNAT" + val serviceMode = "serviceMode" + val modeProxy = "proxy" + val modeVpn = "vpn" + val modeTransproxy = "transproxy" + val portProxy = "portProxy" + val portLocalDns = "portLocalDns" + val portTransproxy = "portTransproxy" + val route = "route" val isAutoConnect = "isAutoConnect" @@ -67,7 +47,6 @@ object Key { val password = "sitekey" val method = "encMethod" val remotePort = "remotePortNum" - val localPort = "localPortNum" val remoteDns = "remoteDns" val plugin = "plugin" @@ -76,19 +55,7 @@ object Key { val dirty = "profileDirty" val tfo = "tcp_fastopen" - val currentVersionCode = "currentVersionCode" -} - -object State { - /** - * This state will never be broadcast by the service. This state is only used to indicate that the current context - * hasn't bound to any context. - */ - val IDLE = 0 - val CONNECTING = 1 - val CONNECTED = 2 - val STOPPING = 3 - val STOPPED = 4 + val assetUpdateTime = "assetUpdateTime" } object Action { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index eae40a19a4..76de82340d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -33,7 +33,8 @@ import android.view.View.MeasureSpec import android.view.{Gravity, View, Window} import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService} +import com.github.shadowsocks.bg.{ProxyService, TransproxyService, VpnService} +import com.github.shadowsocks.BuildConfig import org.xbill.DNS._ import scala.collection.JavaConversions._ @@ -47,11 +48,8 @@ object Utils { def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP def getSignature(context: Context): String = { - val info = context - .getPackageManager - .getPackageInfo(context.getPackageName, PackageManager.GET_SIGNATURES) val mdg = MessageDigest.getInstance("SHA-1") - mdg.update(info.signatures(0).toByteArray) + mdg.update(app.info.signatures(0).toByteArray) new String(Base64.encode(mdg.digest, 0)) } @@ -107,7 +105,7 @@ object Utils { } def resolve(host: String, enableIPv6: Boolean): Option[String] = - (if (enableIPv6 && Utils.isIPv6Support) resolve(host, Type.AAAA) else None).orElse(resolve(host, Type.A)) + (if (enableIPv6 && isIPv6Support) resolve(host, Type.AAAA) else None).orElse(resolve(host, Type.A)) .orElse(resolve(host)) private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String]) @@ -137,8 +135,7 @@ object Utils { } def startSsService(context: Context) { - val intent = - new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]) + val intent = new Intent(context, app.serviceClass) if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent) } def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD)) From 86ebbd0db5c281de4d18980521a3fb3d1251ed47 Mon Sep 17 00:00:00 2001 From: miyon Date: Sat, 11 Nov 2017 11:46:42 +0800 Subject: [PATCH 21/79] fix fragment called onstart twice on click back Removed irrelevant changes. --- .../main/scala/com/github/shadowsocks/MainActivity.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 5bef0c68b8..96c9397608 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -399,10 +399,8 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else { val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment] if (!currentFragment.onBackPressed()) - if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() else { - displayFragment(new ProfilesFragment) - drawer.setSelection(DRAWER_PROFILES) - } + if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() + else drawer.setSelection(DRAWER_PROFILES) } override def onStop() { From b72fb267e37a03fa375bee8fcc0d36edff2de339 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 11 Nov 2017 01:11:05 -0800 Subject: [PATCH 22/79] Prevent fragment recreations --- .../src/main/scala/com/github/shadowsocks/MainActivity.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 96c9397608..894c07fded 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -244,6 +244,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (tf != null) title.setTypeface(tf) if (savedInstanceState == null) displayFragment(new ProfilesFragment) + else previousPosition = drawer.getCurrentSelectedPosition statusText = findViewById(R.id.status).asInstanceOf[TextView] txText = findViewById(R.id.tx).asInstanceOf[TextView] txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] @@ -356,8 +357,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe drawer.closeDrawer() } + private var previousPosition: Int = _ override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = { - drawerItem.getIdentifier match { + if (position == previousPosition) drawer.closeDrawer() else drawerItem.getIdentifier match { case DRAWER_PROFILES => displayFragment(new ProfilesFragment) case DRAWER_RECOVERY => app.track("GlobalConfigFragment", "reset") @@ -379,6 +381,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe case DRAWER_CUSTOM_RULES => displayFragment(new CustomRulesFragment) case _ => // Ignore } + previousPosition = position true // unexpected cases will throw exception } From 967f198131c90f63bdddd2a2a506f0b56a71a743 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 11 Nov 2017 01:42:51 -0800 Subject: [PATCH 23/79] Always initialize previousPosition --- mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 894c07fded..f220ec7578 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -244,7 +244,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (tf != null) title.setTypeface(tf) if (savedInstanceState == null) displayFragment(new ProfilesFragment) - else previousPosition = drawer.getCurrentSelectedPosition + previousPosition = drawer.getCurrentSelectedPosition statusText = findViewById(R.id.status).asInstanceOf[TextView] txText = findViewById(R.id.tx).asInstanceOf[TextView] txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] From 13ef297a03e4d0fadd9b77d482e05ca052e7aa4e Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 12 Nov 2017 14:22:11 -0800 Subject: [PATCH 24/79] Change default DNS port to 5450 to avoid conflicts with Orbot --- .../github/shadowsocks/ShadowsocksApplication.scala | 10 ++++------ .../com/github/shadowsocks/database/Profile.scala | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 1f147de76c..1a55cf7813 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -160,12 +160,10 @@ class ShadowsocksApplication extends Application { if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets() // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat - if (!(1025 to 65535 contains dataStore.portProxy)) - dataStore.putInt(Key.portProxy, 1080 + Binder.getCallingUserHandle.hashCode) - if (!(1025 to 65535 contains dataStore.portLocalDns)) - dataStore.putInt(Key.portLocalDns, 5400 + Binder.getCallingUserHandle.hashCode) - if (!(1025 to 65535 contains dataStore.portTransproxy)) - dataStore.putInt(Key.portTransproxy, 8200 + Binder.getCallingUserHandle.hashCode) + lazy val userIndex = Binder.getCallingUserHandle.hashCode + if (!(1025 to 65535 contains dataStore.portProxy)) dataStore.putInt(Key.portProxy, 1080 + userIndex) + if (!(1025 to 65535 contains dataStore.portLocalDns)) dataStore.putInt(Key.portLocalDns, 5450 + userIndex) + if (!(1025 to 65535 contains dataStore.portTransproxy)) dataStore.putInt(Key.portTransproxy, 8200 + userIndex) if (Build.VERSION.SDK_INT >= 26) { val nm = getSystemService(classOf[NotificationManager]) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index 33d15c0772..38b0741d64 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -23,7 +23,6 @@ package com.github.shadowsocks.database import java.util.Locale import android.net.Uri -import android.os.Binder import android.util.Base64 import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.preference.OrmLitePreferenceDataStore From 79f40b88129afbf4e7e8c57115f1c77cbc341013 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 12 Nov 2017 14:50:19 -0800 Subject: [PATCH 25/79] Implement connection test for proxy modes --- mobile/src/main/res/values/strings.xml | 1 - .../com/github/shadowsocks/MainActivity.scala | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 1754f6a8f9..50ede87eb0 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -127,7 +127,6 @@ Received: Connecting… Connected, tap to check connection - Connected Not connected diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 145ea7c7e5..3a50d02663 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -21,7 +21,7 @@ package com.github.shadowsocks import java.lang.System.currentTimeMillis -import java.net.{HttpURLConnection, URL} +import java.net.{HttpURLConnection, InetSocketAddress, URL, Proxy => JavaProxy} import java.util.Locale import android.app.Activity @@ -119,7 +119,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (state == ServiceState.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.postDelayed(hideCircle, 1000) fab.setImageResource(R.drawable.ic_start_connected) - statusText.setText(if (app.usingVpnMode) R.string.vpn_connected else R.string.nat_connected) + statusText.setText(R.string.vpn_connected) case ServiceState.STOPPING => fab.setImageResource(R.drawable.ic_start_busy) if (state == ServiceState.CONNECTED) fabProgressCircle.show() // ignore for stopped @@ -242,17 +242,21 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] rxText = findViewById(R.id.rx).asInstanceOf[TextView] rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView] - findViewById[View](R.id.stat).setOnClickListener(_ => if (state == ServiceState.CONNECTED && app.usingVpnMode) { + findViewById[View](R.id.stat).setOnClickListener(_ => if (state == ServiceState.CONNECTED) { testCount += 1 statusText.setText(R.string.connection_test_testing) val id = testCount // it would change by other code Utils.ThrowableFuture { // Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640 - autoDisconnect(new URL("https", app.currentProfile.get.route match { - case Acl.CHINALIST => "www.qualcomm.cn" - case _ => "www.google.com" - }, "/generate_204").openConnection() - .asInstanceOf[HttpURLConnection]) { conn => + autoDisconnect { + val url = new URL("https", app.currentProfile.get.route match { + case Acl.CHINALIST => "www.qualcomm.cn" + case _ => "www.google.com" + }, "/generate_204") + (if (app.usingVpnMode) url.openConnection() else url.openConnection( + new JavaProxy(JavaProxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", app.dataStore.portProxy)))) + .asInstanceOf[HttpURLConnection] + } { conn => conn.setConnectTimeout(5 * 1000) conn.setReadTimeout(5 * 1000) conn.setInstanceFollowRedirects(false) From e1a51b0d2200bf070dec194e3864f103ffbbe720 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 12 Nov 2017 15:41:10 -0800 Subject: [PATCH 26/79] Add tutorial for using transproxy mode --- .github/faq.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/faq.md b/.github/faq.md index eb6dcad62c..742e3df3c3 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -65,3 +65,36 @@ To scan the QR code through the integrated QR scanner. By the way, upgrade your Android system already. +### How to use Transproxy mode? + +1. Install [AFWall+](https://github.com/ukanth/afwall); +2. Set custom script: +```sh +IP6TABLES=/system/bin/ip6tables +IPTABLES=/system/bin/iptables +ULIMIT=/system/bin/ulimit +SHADOWSOCKS_UID=`dumpsys package com.github.shadowsocks | grep userId | cut -d= -f2 - | cut -d' ' -f1 -` +PORT_DNS=5450 +PORT_TRANSPROXY=8200 +$ULIMIT -n 4096 +$IP6TABLES -F +$IP6TABLES -A INPUT -j DROP +$IP6TABLES -A OUTPUT -j DROP +$IPTABLES -t nat -F OUTPUT +$IPTABLES -t nat -A OUTPUT -o lo -j RETURN +$IPTABLES -t nat -A OUTPUT -d 127.0.0.1 -j RETURN +$IPTABLES -t nat -A OUTPUT -m owner --uid-owner $SHADOWSOCKS_UID -j RETURN +$IPTABLES -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS +$IPTABLES -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS +$IPTABLES -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY +$IPTABLES -t nat -A OUTPUT -p udp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY +``` +3. Set custom shutdown script: +```sh +IP6TABLES=/system/bin/ip6tables +IPTABLES=/system/bin/iptables +$IPTABLES -t nat -F OUTPUT +$IP6TABLES -F +``` +4. Make sure to allow traffic for Shadowsocks; +5. Start Shadowsocks transproxy service and enable firewall. From 99e5cb8579e5f93cf0d8171835fc0bc19f0f4d84 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 12 Nov 2017 15:57:06 -0800 Subject: [PATCH 27/79] Update issue_template.md --- .github/issue_template.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/issue_template.md b/.github/issue_template.md index 9edc0c994a..d0c695e706 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -15,9 +15,7 @@ _Put an `x` inside the [ ] that applies._ * [ ] IPv6 server address * [ ] Client IPv4 availability * [ ] Client IPv6 availability -* Local port: 1080 * Encrypt method: -* [ ] One-time authentication * Route * [ ] All * [ ] Bypass LAN @@ -27,14 +25,14 @@ _Put an `x` inside the [ ] that applies._ * [ ] China List * [ ] Custom rules * [ ] IPv6 route -* [ ] Per-App Proxy +* [ ] Apps VPN mode * [ ] Bypass mode * Remote DNS: 8.8.8.8 * [ ] DNS Forwarding * Plugin configuration (if applicable): * [ ] Auto Connect * [ ] TCP Fast Open -* [ ] NAT mode +* If you're not using VPN mode, please supply more details here: ### What did you do? From fb2fdd5dea639c42d6da3674fc2adf8045dc209f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 12 Nov 2017 16:10:25 -0800 Subject: [PATCH 28/79] Remove extra spaces As suggested by @wongsyrone. --- mobile/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 0c6884f262..7a27fdc1ef 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:required="false"/> - Date: Tue, 14 Nov 2017 23:50:06 -0800 Subject: [PATCH 29/79] Lift default notification priority for VPN service Priority has been lifted from IMPORTANCE_MIN to IMPORTANCE_LOW. This change only impacts Android 8.0 and higher. Fixes #1431. Also fixes https://github.com/shadowsocks/shadowsocks-android/issues/1037#issuecomment-338367917. --- .../scala/com/github/shadowsocks/ShadowsocksApplication.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 1a55cf7813..66dd9fa7cf 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -168,7 +168,7 @@ class ShadowsocksApplication extends Application { if (Build.VERSION.SDK_INT >= 26) { val nm = getSystemService(classOf[NotificationManager]) nm.createNotificationChannels(List( - new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), + new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_LOW), new NotificationChannel("service-proxy", getText(R.string.service_proxy), NotificationManager.IMPORTANCE_LOW), new NotificationChannel("service-transproxy", getText(R.string.service_transproxy), NotificationManager.IMPORTANCE_LOW) From 59a6eaac56144accf3a79a84e38d88d27c6ef5b8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 15 Nov 2017 00:10:53 -0800 Subject: [PATCH 30/79] Remove ACTION_USER_PRESENT listener on API 26+ This is removed since we can't change notification priority dynamically since API 26 and this also prevents an extraneous system notification. NB: After this change, `visible` doesn't have any observable effect on API 26+. --- .../scala/com/github/shadowsocks/bg/ServiceNotification.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala index 252e585305..0c8458f2e5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala @@ -72,7 +72,7 @@ class ServiceNotification(private val service: BaseService, profileName: String, val screenFilter = new IntentFilter() screenFilter.addAction(Intent.ACTION_SCREEN_ON) screenFilter.addAction(Intent.ACTION_SCREEN_OFF) - if (visible && Utils.isLollipopOrAbove) screenFilter.addAction(Intent.ACTION_USER_PRESENT) + if (visible && 21 until 26 contains Build.VERSION.SDK_INT) screenFilter.addAction(Intent.ACTION_USER_PRESENT) service.registerReceiver(lockReceiver, screenFilter) private def update(action: String, forceShow: Boolean = false) = From b1c8d3c617c5ea58e0c99ea7fb5e7fbeb1a695da Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 15 Nov 2017 00:37:24 -0800 Subject: [PATCH 31/79] Fix proxy notification not displaying on screen on in Android 4.4 --- .../scala/com/github/shadowsocks/bg/ServiceNotification.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala index 0c8458f2e5..d5b04cb7cd 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala @@ -78,10 +78,10 @@ class ServiceNotification(private val service: BaseService, profileName: String, private def update(action: String, forceShow: Boolean = false) = if (forceShow || service.getState == ServiceState.CONNECTED) action match { case Intent.ACTION_SCREEN_OFF => - setVisible(visible && !Utils.isLollipopOrAbove, forceShow) + setVisible(visible && Build.VERSION.SDK_INT < 21, forceShow) unregisterCallback() // unregister callback to save battery case Intent.ACTION_SCREEN_ON => - setVisible(visible && Utils.isLollipopOrAbove && !keyGuard.inKeyguardRestrictedInputMode, forceShow) + setVisible(visible && (Build.VERSION.SDK_INT < 21 || !keyGuard.inKeyguardRestrictedInputMode), forceShow) service.binder.registerCallback(callback) service.binder.startListeningForBandwidth(callback) callbackRegistered = true From e0bf727bf49f49266bb2e455558bddd0623856a7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 15 Nov 2017 00:40:14 -0800 Subject: [PATCH 32/79] Add missing parentheses --- .../scala/com/github/shadowsocks/bg/ServiceNotification.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala index d5b04cb7cd..e8ced018ef 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/ServiceNotification.scala @@ -72,7 +72,7 @@ class ServiceNotification(private val service: BaseService, profileName: String, val screenFilter = new IntentFilter() screenFilter.addAction(Intent.ACTION_SCREEN_ON) screenFilter.addAction(Intent.ACTION_SCREEN_OFF) - if (visible && 21 until 26 contains Build.VERSION.SDK_INT) screenFilter.addAction(Intent.ACTION_USER_PRESENT) + if (visible && (21 until 26 contains Build.VERSION.SDK_INT)) screenFilter.addAction(Intent.ACTION_USER_PRESENT) service.registerReceiver(lockReceiver, screenFilter) private def update(action: String, forceShow: Boolean = false) = From 42e930391f44b90c887a40350d34d4b501dbf0c3 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 15 Nov 2017 21:00:53 -0800 Subject: [PATCH 33/79] Output more information on failure of preparing VpnService Fix #1424. --- mobile/src/main/res/values/strings.xml | 1 + .../src/main/scala/com/github/shadowsocks/MainActivity.scala | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 50ede87eb0..f20e141213 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -72,6 +72,7 @@ Stop Shutting down… %s + Permission denied to create a VPN service Failed to start VPN service. You might need to reboot your device. No valid profile data found. diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index f58ba3ae7f..df9a3a7d18 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -167,7 +167,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match { case Activity.RESULT_OK => Utils.startSsService(this) - case _ => Log.e(TAG, "Failed to start VpnService") + case _ => + Snackbar.make(findViewById(R.id.snackbar), R.string.vpn_permission_denied, Snackbar.LENGTH_LONG).show() + Log.e(TAG, "Failed to start VpnService: %s".formatLocal(Locale.ENGLISH, data.toString)) } override def onCreate(savedInstanceState: Bundle) { From 2733d61003b621f77771ec34dbf9d30e58ce86ea Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 16 Nov 2017 01:24:42 -0800 Subject: [PATCH 34/79] Update libevent to 2.1.8 --- mobile/src/main/jni/Android.mk | 16 ++++------------ mobile/src/main/jni/jni-helper.cpp | 1 + mobile/src/main/jni/libevent | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index ccc3b4738b..ee7c03c32b 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -85,21 +85,13 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LIBEVENT_SOURCES := \ - buffer.c \ - bufferevent.c bufferevent_filter.c \ - bufferevent_pair.c bufferevent_ratelim.c \ - bufferevent_sock.c epoll.c \ - epoll_sub.c evdns.c event.c \ - event_tagging.c evmap.c \ - evrpc.c evthread.c \ - evthread_pthread.c evutil.c \ - evutil_rand.c http.c \ - listener.c log.c poll.c \ - select.c signal.c strlcpy.c + buffer.c bufferevent.c event.c \ + bufferevent_sock.c bufferevent_ratelim.c \ + evthread.c log.c evutil.c evutil_time.c evmap.c epoll.c poll.c signal.c select.c LOCAL_MODULE := event LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) -LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \ +LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libevent \ -I$(LOCAL_PATH)/libevent/include \ include $(BUILD_STATIC_LIBRARY) diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index 60bac6b19a..bf017acd6d 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/mobile/src/main/jni/libevent b/mobile/src/main/jni/libevent index 359ca847a6..f29f07bc8c 160000 --- a/mobile/src/main/jni/libevent +++ b/mobile/src/main/jni/libevent @@ -1 +1 @@ -Subproject commit 359ca847a649b9c318f9217c0755484d98ecb779 +Subproject commit f29f07bc8c43eec96f227e6f6eede32b3af66168 From dea3aa1c19d8cc4ee13be00409fb318f05f652c5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 16 Nov 2017 01:26:15 -0800 Subject: [PATCH 35/79] Update libsodium to 1.0.15 --- mobile/src/main/jni/libsodium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/libsodium b/mobile/src/main/jni/libsodium index 70170c28c8..c5e43f4c1c 160000 --- a/mobile/src/main/jni/libsodium +++ b/mobile/src/main/jni/libsodium @@ -1 +1 @@ -Subproject commit 70170c28c844a4786e75efc626e1aeebc93caebc +Subproject commit c5e43f4c1cf62b4669b1f33516fc33ef2d005187 From 5ad2e9a55db9b91f5b914f6acd6ca82c9f0c9c37 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 16 Nov 2017 01:29:22 -0800 Subject: [PATCH 36/79] Update mbedtls to 2.6.1 --- mobile/src/main/jni/mbedtls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/mbedtls b/mobile/src/main/jni/mbedtls index 1e4ec667a4..4f0929189a 160000 --- a/mobile/src/main/jni/mbedtls +++ b/mobile/src/main/jni/mbedtls @@ -1 +1 @@ -Subproject commit 1e4ec667a4dd5f06ccc41d69cdef3e07f92fa242 +Subproject commit 4f0929189ab43e020b0d441919abf6bab02baf11 From c5e9e6a81c44bd6662c13e4d532e91d75ea7b43c Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 16 Nov 2017 20:09:40 -0800 Subject: [PATCH 37/79] Update the string --- mobile/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index f20e141213..7c44d69951 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -50,8 +50,8 @@ GFW List China List Apps VPN mode - Allow selected apps to bypass VPN, not available on Android 4.x - Allow selected apps to bypass VPN + Configure VPN mode for selected apps, not available on Android 4.x + Configure VPN mode for selected apps On Bypass Mode Enable this option to bypass selected apps From b510e0ab58dc5654c068da297741adbd8f1700c8 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 16 Nov 2017 21:56:47 -0800 Subject: [PATCH 38/79] Fix #1452 --- mobile/src/main/AndroidManifest.xml | 8 ++++---- .../com/github/shadowsocks/ServiceBoundContext.scala | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 7a27fdc1ef..6986bd018d 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -82,10 +82,10 @@ android:excludeFromRecents="true" android:launchMode="singleTask"/> - } - listeningForBandwidth = false callbackRegistered = false } From 7154f9fdbaeef52d3ded2f3281faa88db04beb13 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 16 Nov 2017 22:05:53 -0800 Subject: [PATCH 39/79] Fix a null dereferencing exception --- mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index df9a3a7d18..6764a107cb 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -169,7 +169,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe case Activity.RESULT_OK => Utils.startSsService(this) case _ => Snackbar.make(findViewById(R.id.snackbar), R.string.vpn_permission_denied, Snackbar.LENGTH_LONG).show() - Log.e(TAG, "Failed to start VpnService: %s".formatLocal(Locale.ENGLISH, data.toString)) + Log.e(TAG, "Failed to start VpnService: %s".formatLocal(Locale.ENGLISH, data)) } override def onCreate(savedInstanceState: Bundle) { From 3f92857e5232af113e5fa9d0d803cfb3ba843b80 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 17 Nov 2017 18:38:22 -0800 Subject: [PATCH 40/79] Recommend to use Always-on VPN on Android 7+ --- mobile/src/main/res/values-v24/strings.xml | 4 ++++ mobile/src/main/res/values/strings.xml | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 mobile/src/main/res/values-v24/strings.xml diff --git a/mobile/src/main/res/values-v24/strings.xml b/mobile/src/main/res/values-v24/strings.xml new file mode 100644 index 0000000000..d7c49ffd3d --- /dev/null +++ b/mobile/src/main/res/values-v24/strings.xml @@ -0,0 +1,4 @@ + + + @string/auto_connect_summary_v24 + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 7c44d69951..19fb1f7193 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -57,6 +57,8 @@ Enable this option to bypass selected apps Auto Connect Enable Shadowsocks on startup + Enable Shadowsocks on startup. Recommended to use always-on VPN + instead Toggling requires ROOT permission Unsupported kernel version: %s < 3.7.1 DNS Forwarding From f4eeab0548f625a3847b3af96bcfa04852292503 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 17 Nov 2017 19:20:05 -0800 Subject: [PATCH 41/79] Remove apps VPN mode option for Android 4.4 --- mobile/src/main/res/values-v21/strings.xml | 4 ---- mobile/src/main/res/values/strings.xml | 3 +-- .../github/shadowsocks/ProfileConfigFragment.scala | 14 ++++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 mobile/src/main/res/values-v21/strings.xml diff --git a/mobile/src/main/res/values-v21/strings.xml b/mobile/src/main/res/values-v21/strings.xml deleted file mode 100644 index 4cb3b9a096..0000000000 --- a/mobile/src/main/res/values-v21/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - @string/proxied_apps_summary_v21 - diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 19fb1f7193..8bb3c62cba 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -50,8 +50,7 @@ GFW List China List Apps VPN mode - Configure VPN mode for selected apps, not available on Android 4.x - Configure VPN mode for selected apps + Configure VPN mode for selected apps On Bypass Mode Enable this option to bypass selected apps diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 7675a68d37..8e1272522d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -66,12 +66,14 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe findPreference(Key.password).setSummary("\u2022" * 32) } isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setEnabled(Utils.isLollipopOrAbove && app.usingVpnMode) - isProxyApps.setOnPreferenceClickListener(_ => { - startActivity(new Intent(getActivity, classOf[AppManager])) - isProxyApps.setChecked(true) - false - }) + if (Build.VERSION.SDK_INT < 21) isProxyApps.getParent.removePreference(isProxyApps) else { + isProxyApps.setEnabled(app.usingVpnMode) + isProxyApps.setOnPreferenceClickListener(_ => { + startActivity(new Intent(getActivity, classOf[AppManager])) + isProxyApps.setChecked(true) + false + }) + } plugin = findPreference(Key.plugin).asInstanceOf[IconListPreference] pluginConfigure = findPreference(Key.pluginConfigure).asInstanceOf[EditTextPreference] plugin.unknownValueSummary = getString(R.string.plugin_unknown) From f59f7137f1066c7ea57a766a0c02f430ba45eaa4 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 18 Nov 2017 16:06:29 +0800 Subject: [PATCH 42/79] Replace CardView with normal list view --- .../main/res/drawable/background_profile.xml | 3 -- mobile/src/main/res/layout/layout_profile.xml | 36 ++++++++++--------- .../github/shadowsocks/ProfilesFragment.scala | 10 ++++-- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/mobile/src/main/res/drawable/background_profile.xml b/mobile/src/main/res/drawable/background_profile.xml index d1fba8c50d..daa7f88e7f 100644 --- a/mobile/src/main/res/drawable/background_profile.xml +++ b/mobile/src/main/res/drawable/background_profile.xml @@ -3,7 +3,6 @@ - @@ -12,7 +11,6 @@ - @@ -20,7 +18,6 @@ - diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 3ba5007096..c22c26f409 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -1,21 +1,23 @@ - - + + @@ -56,8 +59,7 @@ + android:layout_marginStart="8dp"> - - + + diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index f3efa8dae8..efcdd70808 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -135,15 +135,16 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic val params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) params.gravity = Gravity.CENTER_HORIZONTAL - params.setMargins(0, getResources.getDimensionPixelOffset(R.dimen.margin_small), 0, 0) + params.setMargins(0, 0, 0, 0) adView = new AdView(getActivity) adView.setLayoutParams(params) adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") - adView.setAdSize(AdSize.LARGE_BANNER) - itemView.findViewById[LinearLayout](R.id.content).addView(adView) + adView.setAdSize(AdSize.SMART_BANNER) + itemView.findViewById[LinearLayout](R.id.ads).addView(adView) // Load Ad val adBuilder = new AdRequest.Builder() + adBuilder.addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208") adView.loadAd(adBuilder.build()) } else adView.setVisibility(View.VISIBLE) } else if (adView != null) adView.setVisibility(View.GONE) @@ -266,7 +267,10 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic if (app.profileManager.getFirstProfile.isEmpty) app.dataStore.profileId = app.profileManager.createProfile().id val profilesList = view.findViewById[RecyclerView](R.id.list) val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false) + val dividerItemDecoration = new DividerItemDecoration(profilesList.getContext(), + layoutManager.getOrientation()) profilesList.setLayoutManager(layoutManager) + profilesList.addItemDecoration(dividerItemDecoration) layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { case (profile, i) if profile.id == app.dataStore.profileId => i }.getOrElse(-1)) From 5505c78fdcfd96dd04e0f95c55f489e2ac561663 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 00:53:17 -0800 Subject: [PATCH 43/79] Refine 'Replace CardView with normal list view' --- mobile/src/main/res/layout/layout_profile.xml | 148 +++++++++--------- .../github/shadowsocks/ProfilesFragment.scala | 9 +- .../shadowsocks/widget/BoundedCardView.scala | 32 ---- 3 files changed, 78 insertions(+), 111 deletions(-) delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index c22c26f409..244085786e 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -1,84 +1,88 @@ - - - + - + - - - - - + - - - + android:padding="8dp" + android:layout_gravity="top" + app:srcCompat="@drawable/ic_social_share" + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/share" + android:focusable="true" + android:nextFocusLeft="@+id/container"/> + - + + + + + diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index efcdd70808..9108817b5c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -78,8 +78,6 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic edit.setOnClickListener(_ => startConfig(item.id)) edit.setOnLongClickListener(cardButtonLongClickListener) itemView.setOnClickListener(this) - // it will not take effect unless set in code - itemView.findViewById[View](R.id.indicator).setBackgroundResource(R.drawable.background_profile) private var adView: AdView = _ @@ -135,12 +133,11 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic val params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) params.gravity = Gravity.CENTER_HORIZONTAL - params.setMargins(0, 0, 0, 0) adView = new AdView(getActivity) adView.setLayoutParams(params) adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") adView.setAdSize(AdSize.SMART_BANNER) - itemView.findViewById[LinearLayout](R.id.ads).addView(adView) + itemView.findViewById[LinearLayout](R.id.indicator).addView(adView) // Load Ad val adBuilder = new AdRequest.Builder() @@ -267,10 +264,8 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic if (app.profileManager.getFirstProfile.isEmpty) app.dataStore.profileId = app.profileManager.createProfile().id val profilesList = view.findViewById[RecyclerView](R.id.list) val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false) - val dividerItemDecoration = new DividerItemDecoration(profilesList.getContext(), - layoutManager.getOrientation()) profilesList.setLayoutManager(layoutManager) - profilesList.addItemDecoration(dividerItemDecoration) + profilesList.addItemDecoration(new DividerItemDecoration(getActivity, layoutManager.getOrientation)) layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { case (profile, i) if profile.id == app.dataStore.profileId => i }.getOrElse(-1)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala b/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala deleted file mode 100644 index b8e97a7bca..0000000000 --- a/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala +++ /dev/null @@ -1,32 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package com.github.shadowsocks.widget - -import android.content.Context -import android.support.v7.widget.CardView -import android.util.AttributeSet - -/** - * @author Mygod - */ -class BoundedCardView(context: Context, attrs: AttributeSet) extends CardView(context, attrs) with BoundedView { - initAttrs(context, attrs) -} From 987b545cb2cd09d7edb2bc213e4e225725c94ac8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 00:58:05 -0800 Subject: [PATCH 44/79] Fix missing padding for traffic counter --- mobile/src/main/res/layout/layout_profile.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 244085786e..807a5848e4 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -62,7 +62,8 @@ + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp"> Date: Sat, 18 Nov 2017 01:06:38 -0800 Subject: [PATCH 45/79] Refine spacing --- mobile/src/main/res/layout/layout_profile.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 807a5848e4..821efa8cd5 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingLeft="16dp" + android:paddingLeft="8dp" android:paddingRight="4dp" android:paddingTop="8dp" android:paddingBottom="8dp" @@ -71,6 +71,7 @@ android:layout_alignParentStart="true" android:layout_toStartOf="@+id/traffic" android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp" android:maxLines="2" android:ellipsize="end" android:textAppearance="?android:attr/textAppearanceSmall" @@ -81,6 +82,7 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_alignParentEnd="true" + android:layout_marginBottom="8dp" android:ellipsize="end" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary"/> From dc2a5ca19c93df11e0658624cb517fe0de2fad3f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 01:47:44 -0800 Subject: [PATCH 46/79] Use SIGKILL to kill old processes Fix #1455. Note that in later Android versions, only processes under same uid will be listed and this will be quite efficient. --- .../com/github/shadowsocks/JniHelper.java | 1 + mobile/src/main/jni/jni-helper.cpp | 74 ++++--------------- .../github/shadowsocks/bg/Executable.scala | 25 +++++-- 3 files changed, 32 insertions(+), 68 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java index c2299e583e..2cc41a34e1 100755 --- a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -64,6 +64,7 @@ public static boolean waitForCompat(Process process, long millis) throws Excepti } } + public static native int sigkill(int pid); private static native int sigterm(Process process); private static native Integer getExitValue(Process process); private static native Object getExitValueMutex(Process process); diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index bf017acd6d..6c56cb52e5 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -31,7 +31,13 @@ static int sdk_version() { return atoi(version); } -jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jobject process) { +extern "C" { +JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) { + // Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed. + return kill(pid, SIGKILL) == -1 && errno != ESRCH ? errno : 0; +} + +JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigterm(JNIEnv *env, jobject thiz, jobject process) { if (!env->IsInstanceOf(process, ProcessImpl)) { THROW(env, "java/lang/ClassCastException", "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); @@ -42,7 +48,8 @@ jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jo return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; } -jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) { +JNIEXPORT jobject JNICALL + Java_com_github_shadowsocks_JniHelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) { if (!env->IsInstanceOf(process, ProcessImpl)) { THROW(env, "java/lang/ClassCastException", "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); @@ -51,7 +58,8 @@ jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject return env->GetObjectField(process, ProcessImpl_exitValue); } -jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) { +JNIEXPORT jobject JNICALL + Java_com_github_shadowsocks_JniHelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) { if (!env->IsInstanceOf(process, ProcessImpl)) { THROW(env, "java/lang/ClassCastException", "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); @@ -60,11 +68,12 @@ jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, job return env->GetObjectField(process, ProcessImpl_exitValueMutex); } -void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { +JNIEXPORT void JNICALL Java_com_github_shadowsocks_JniHelper_close(JNIEnv *env, jobject thiz, jint fd) { close(fd); } -jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { +JNIEXPORT jint JNICALL + Java_com_github_shadowsocks_JniHelper_sendFd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { int fd; struct sockaddr_un addr; const char *sock_str = env->GetStringUTFChars(path, 0); @@ -94,56 +103,6 @@ jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jin env->ReleaseStringUTFChars(path, sock_str); return 0; } - -static const char *classPathName = "com/github/shadowsocks/JniHelper"; - -static JNINativeMethod method_table[] = { - { "close", "(I)V", - (void*) Java_com_github_shadowsocks_jnihelper_close }, - { "sendFd", "(ILjava/lang/String;)I", - (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, - { "sigterm", "(Ljava/lang/Process;)I", - (void*) Java_com_github_shadowsocks_jnihelper_sigterm }, - { "getExitValue", "(Ljava/lang/Process;)Ljava/lang/Integer;", - (void*) Java_com_github_shadowsocks_jnihelper_getExitValue }, - { "getExitValueMutex", "(Ljava/lang/Process;)Ljava/lang/Object;", - (void*) Java_com_github_shadowsocks_jnihelper_getExitValueMutex } -}; - -/* - * Register several native methods for one class. - */ -static int registerNativeMethods(JNIEnv* env, const char* className, - JNINativeMethod* gMethods, int numMethods) -{ - jclass clazz; - - clazz = env->FindClass(className); - if (clazz == NULL) { - LOGE("Native registration unable to find class '%s'", className); - return JNI_FALSE; - } - if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { - LOGE("RegisterNatives failed for '%s'", className); - return JNI_FALSE; - } - - return JNI_TRUE; -} - -/* - * Register native methods for all classes we know about. - * - * returns JNI_TRUE on success. - */ -static int registerNatives(JNIEnv* env) -{ - if (!registerNativeMethods(env, classPathName, method_table, - sizeof(method_table) / sizeof(method_table[0]))) { - return JNI_FALSE; - } - - return JNI_TRUE; } /* @@ -167,11 +126,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { } env = uenv.env; - if (registerNatives(env) != JNI_TRUE) { - THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed"); - goto bail; - } - if (sdk_version() < 24) { if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala index 7d2a1e3832..d024b74791 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala @@ -1,9 +1,14 @@ package com.github.shadowsocks.bg +import java.io.File import java.util.Locale -import java.util.concurrent.TimeUnit +import android.text.TextUtils import android.util.Log +import com.github.shadowsocks.JniHelper +import com.github.shadowsocks.ShadowsocksApplication.app + +import scala.io.Source /** * @author Mygod @@ -16,12 +21,16 @@ object Executable { val TUN2SOCKS = "libtun2socks.so" val OVERTURE = "liboverture.so" - val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE) + val EXECUTABLES = Set(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE) - def killAll() { - val killer = new ProcessBuilder("killall" +: EXECUTABLES: _*).start() - if (!killer.waitFor(1, TimeUnit.SECONDS)) - Log.w("killall", "%s didn't exit within 1s. Post-crash clean-up may have failed." - .formatLocal(Locale.ENGLISH, killer.toString)) - } + def killAll(): Unit = + for (process <- new File("/proc").listFiles((_, name) => TextUtils.isDigitsOnly(name))) { + val exe = new File(Source.fromFile(new File(process, "cmdline")).mkString.split("\0", 2).head) + if (exe.getParent == app.getApplicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.getName)) + JniHelper.sigkill(process.getName.toInt) match { + case 0 => + case errno => Log.w("kill", "SIGKILL %s (%d) failed with %d" + .formatLocal(Locale.ENGLISH, exe.getAbsolutePath, process.getName, errno)) + } + } } From 7b4cae279415c8c04c991630b9ae2523ce6810ea Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 18 Nov 2017 18:15:12 +0800 Subject: [PATCH 47/79] Refine AdView --- build.sbt | 1 - .../scala/com/github/shadowsocks/ProfilesFragment.scala | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 40185dbe74..e1b5bcefae 100644 --- a/build.sbt +++ b/build.sbt @@ -41,7 +41,6 @@ lazy val mobile = project .settings(commonSettings) .settings( libraryDependencies ++= - "com.android.support" % "cardview-v7" % supportLibsVersion :: "com.android.support" % "customtabs" % supportLibsVersion :: "com.android.support" % "design" % supportLibsVersion :: "com.android.support" % "gridlayout-v7" % supportLibsVersion :: diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 9108817b5c..dc82deaa39 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -21,6 +21,7 @@ package com.github.shadowsocks import android.content._ +import android.content.res.Configuration import android.os.Bundle import android.support.design.widget.Snackbar import android.support.v7.widget.RecyclerView.ViewHolder @@ -136,7 +137,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic adView = new AdView(getActivity) adView.setLayoutParams(params) adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") - adView.setAdSize(AdSize.SMART_BANNER) + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) + adView.setAdSize(AdSize.FULL_BANNER) + else + adView.setAdSize(AdSize.SMART_BANNER) + itemView.findViewById[LinearLayout](R.id.indicator).addView(adView) // Load Ad From 3cb6bb73b3a8137af3e1cf4f3251cb83754e5700 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 13:04:26 -0800 Subject: [PATCH 48/79] Add a workaround for a support library bug --- .../main/scala/com/github/shadowsocks/GlobalConfigFragment.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala index 047a49709e..53654490c3 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala @@ -32,6 +32,7 @@ import com.github.shadowsocks.utils.{Key, TcpFastOpen} class GlobalConfigFragment extends PreferenceFragment { override def onCreatePreferences(bundle: Bundle, key: String) { getPreferenceManager.setPreferenceDataStore(app.dataStore) + app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug addPreferencesFromResource(R.xml.pref_global) val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] switch.setOnPreferenceChangeListener((_, value) => { From f34770f3fb94572687422a1416a59cba629ac7ab Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 13:52:35 -0800 Subject: [PATCH 49/79] Fix meta-data not in application --- mobile/src/main/AndroidManifest.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 6986bd018d..313010a6ad 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -19,9 +19,6 @@ android:minSdkVersion="19" android:targetSdkVersion="27"/> - - + Date: Sun, 19 Nov 2017 10:51:34 +0800 Subject: [PATCH 50/79] Update overture --- mobile/src/overture/src/github.com/shadowsocks/overture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/overture/src/github.com/shadowsocks/overture b/mobile/src/overture/src/github.com/shadowsocks/overture index 943e9dcb62..c8ec480cf7 160000 --- a/mobile/src/overture/src/github.com/shadowsocks/overture +++ b/mobile/src/overture/src/github.com/shadowsocks/overture @@ -1 +1 @@ -Subproject commit 943e9dcb62c47e9016b39185c4d92592e05b6fbb +Subproject commit c8ec480cf716be70bc3cae2c4cd7f2733901ab09 From f5036bbde5bb51e1dfd1207fb0b232ac02213d2d Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 01:16:53 -0800 Subject: [PATCH 51/79] Refrain from using SMART_BANNER to prevent its weirdness --- .../main/res/drawable/background_profile.xml | 1 + mobile/src/main/res/layout/layout_profile.xml | 145 +++++++++--------- .../github/shadowsocks/ProfilesFragment.scala | 9 +- 3 files changed, 72 insertions(+), 83 deletions(-) diff --git a/mobile/src/main/res/drawable/background_profile.xml b/mobile/src/main/res/drawable/background_profile.xml index daa7f88e7f..d6fb147ad2 100644 --- a/mobile/src/main/res/drawable/background_profile.xml +++ b/mobile/src/main/res/drawable/background_profile.xml @@ -20,4 +20,5 @@ + diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 821efa8cd5..3111d19108 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -3,89 +3,82 @@ + android:layout_height="wrap_content" + android:paddingLeft="8dp" + android:paddingRight="4dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:focusable="true" + android:nextFocusRight="@+id/edit"> - + - - - - - + - - - + android:padding="8dp" + android:layout_gravity="top" + app:srcCompat="@drawable/ic_social_share" + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/share" + android:focusable="true" + android:nextFocusLeft="@+id/container"/> + + + + + diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index dc82deaa39..dcf59768e6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -21,7 +21,6 @@ package com.github.shadowsocks import android.content._ -import android.content.res.Configuration import android.os.Bundle import android.support.design.widget.Snackbar import android.support.v7.widget.RecyclerView.ViewHolder @@ -137,13 +136,9 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic adView = new AdView(getActivity) adView.setLayoutParams(params) adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") + adView.setAdSize(AdSize.FLUID) - if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) - adView.setAdSize(AdSize.FULL_BANNER) - else - adView.setAdSize(AdSize.SMART_BANNER) - - itemView.findViewById[LinearLayout](R.id.indicator).addView(adView) + itemView.findViewById[LinearLayout](R.id.content).addView(adView) // Load Ad val adBuilder = new AdRequest.Builder() From 197ef557a3414b71cb5f714f768aeb0e20b0beac Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 01:21:19 -0800 Subject: [PATCH 52/79] Add link to releases in README Fix #1460. The original change is dismissed because of usage of possibly copyrighted material. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 61f65178f1..87fa91b338 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ## Shadowsocks for Android -A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. - +A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. [Get latest apk here](https://github.com/shadowsocks/shadowsocks-android/releases) or ### CI STATUS From d4152856f4b7ae57769921754cbebe0c2d72a953 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 01:26:51 -0800 Subject: [PATCH 53/79] Refine README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 87fa91b338..87d8d0a234 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ ## Shadowsocks for Android -A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. [Get latest apk here](https://github.com/shadowsocks/shadowsocks-android/releases) or - +[![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) -### CI STATUS +A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. + + or [releases](https://github.com/shadowsocks/shadowsocks-android/releases). -[![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) ### PREREQUISITES From 6bd2d077ac477d8f48753e42c9d4e2d6683bd1f6 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 01:28:23 -0800 Subject: [PATCH 54/79] Remove an empty line --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 87d8d0a234..6e173832b7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) -A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. - +A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. or [releases](https://github.com/shadowsocks/shadowsocks-android/releases). From 6d6ffba83bb9944b0cd9c9c981c52540b353b96c Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 01:47:05 -0800 Subject: [PATCH 55/79] Update dependencies --- build.sbt | 4 ++-- mobile/build.sbt | 14 +++++++------- .../overture/src/github.com/shadowsocks/overture | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index e1b5bcefae..d45836736d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ lazy val commonSettings = Seq( - scalaVersion := "2.11.11", + scalaVersion := "2.11.12", dexMaxHeap := "4g", organization := "com.github.shadowsocks", @@ -23,7 +23,7 @@ lazy val commonSettings = Seq( resolvers += "google" at "https://maven.google.com" ) -val supportLibsVersion = "27.0.0" +val supportLibsVersion = "27.0.1" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) diff --git a/mobile/build.sbt b/mobile/build.sbt index d89a4338e4..b4e45a016b 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -23,7 +23,7 @@ proguardOptions ++= "-keep public class com.evernote.android.job.JobRescheduleService" :: Nil -val playServicesVersion = "11.4.2" +val playServicesVersion = "11.6.0" resolvers += Resolver.jcenterRepo libraryDependencies ++= "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: @@ -34,12 +34,12 @@ libraryDependencies ++= "com.google.android.gms" % "play-services-gcm" % playServicesVersion :: "com.google.firebase" % "firebase-config" % playServicesVersion :: "com.j256.ormlite" % "ormlite-android" % "5.0" :: - "com.mikepenz" % "crossfader" % "1.5.0" :: - "com.mikepenz" % "fastadapter" % "2.6.3" :: - "com.mikepenz" % "iconics-core" % "2.9.3" :: - "com.mikepenz" % "materialdrawer" % "5.9.5" :: - "com.mikepenz" % "materialize" % "1.0.3" :: - "com.squareup.okhttp3" % "okhttp" % "3.9.0" :: + "com.mikepenz" % "crossfader" % "1.5.1" :: + "com.mikepenz" % "fastadapter" % "3.0.4" :: + "com.mikepenz" % "iconics-core" % "3.0.0" :: + "com.mikepenz" % "materialdrawer" % "6.0.2" :: + "com.mikepenz" % "materialize" % "1.1.2" :: + "com.squareup.okhttp3" % "okhttp" % "3.9.1" :: "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: "dnsjava" % "dnsjava" % "2.1.8" :: "eu.chainfire" % "libsuperuser" % "1.0.0.201704021214" :: diff --git a/mobile/src/overture/src/github.com/shadowsocks/overture b/mobile/src/overture/src/github.com/shadowsocks/overture index c8ec480cf7..943e9dcb62 160000 --- a/mobile/src/overture/src/github.com/shadowsocks/overture +++ b/mobile/src/overture/src/github.com/shadowsocks/overture @@ -1 +1 @@ -Subproject commit c8ec480cf716be70bc3cae2c4cd7f2733901ab09 +Subproject commit 943e9dcb62c47e9016b39185c4d92592e05b6fbb From 1f1a60c27972bb243ac8fd4740b16fbe35d2f377 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 19 Nov 2017 02:09:17 -0800 Subject: [PATCH 56/79] Remove obsolete string --- mobile/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 8bb3c62cba..973b61e7c4 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -35,7 +35,6 @@ Profile Name Server Remote Port - Local Port Password Encrypt Method From 2f9bfb90507e560ac44b088bfb9e4cdd7ed6feef Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 22 Nov 2017 01:39:49 -0800 Subject: [PATCH 57/79] Update string from POEditor --- mobile/src/main/res/values-ja/strings.xml | 62 +++++++++----- mobile/src/main/res/values-ko/strings.xml | 73 ++++++++++++++--- mobile/src/main/res/values-ru/strings.xml | 60 ++++++++++---- mobile/src/main/res/values-zh-rCN/strings.xml | 80 +++++++++++++------ mobile/src/main/res/values-zh-rTW/strings.xml | 53 ++++++++---- 5 files changed, 244 insertions(+), 84 deletions(-) diff --git a/mobile/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml index c4a9159ec5..c60609c2a0 100644 --- a/mobile/src/main/res/values-ja/strings.xml +++ b/mobile/src/main/res/values-ja/strings.xml @@ -1,36 +1,47 @@ +"Shadowsocks" "ON/OFF" "プロファイル" "新規作成または既存ファイルを開く" -"NAT モード (デバックのみ)" -"VPN モードから NAT モードに切り替えるには ROOT 権限が必要です" "リモートDNS" "送信済み: \t%3$s\t↑\t%1$s/s 受信済み: \t%4$s\t↓\t%2$s/s" +"%1$s↑\t%2$s↓" +"%1$s/s↑\t%2$s/s↓" + +"Byte" + "接続状況確認" "テスト中…" "成功: %dmsの遅延" "失敗しました:%s" "インターネット利用不可" -"ステータスコード無効(#%d)" +"ステータスコード無効: #%d" + + +"サーバー設定" "サーバー名" "サーバーアドレス" "リモートポート" -"ローカルポート" "パスワード" "暗号化方式" + +"ファンクション設定" + "IPv6 プロキシ" "リモートサーバーに IPv6 パケットを転送" "プロキシ方式" "中国本土からアクセス不可なアドレス以外を迂回する" + "アプリ別のプロキシを使用" + "アプリ別でプロキシを指定、Android 4.x では必ずNATモードを使用して下さい" "ON" "バイパスモード(迂回モード)" @@ -46,28 +57,28 @@ "バックグラウンドでサービスを開始しました" "サーバーが無効です" "リモートサーバーに接続できません" -"注意:Android 5.0 及びそれ以降のバージョンでは NAT モードの使用はお勧めしません" -"NAT モードは ROOT 権限が必要です" -"VPN モードに切り替える" "中止" "停止中……" "バックグランドサービスの起動に失敗しました:%s" "VPN サービスの起動に失敗しました、デバイスの再起動を試みて下さい" "有効なプロファイルが見つかりません" + +"はい" +"いいえ" + "閉じる" "プロファイルを選択して下さい" "サーバアドレスやパスワードの入力が必要です" "接続" -"リセット中……" -"このプロファイルを削除「%s」?" +"このプロファイルを削除 %s?" "プロファイル" "オプション設定" "よくある質問" -"リセット" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "本アプリについて" "Shadowsocks %s" "編集" @@ -83,14 +94,16 @@ "プロファイル編集" +"変更は適応されておりません、保存しますか?" +"適用" "削除" "このプロファイルを削除しますか" "QR コード / NFC" "Shadowsocks用プロファイルを追加しますか" "QR コードを読み取る" -"手動設定" -"削除済み" +"削除しました +%d 個のアイテムを削除しました" "元に戻す" @@ -106,7 +119,6 @@ "受信済み:" "接続中…" "接続済み、タップして接続状況をチェック" -"接続済み" "未接続" @@ -119,20 +131,34 @@ "中国本土のアドレスを迂回する" "LAN 及び中国本土のアドレスを迂回する" "中国本土のアドレス以外を迂回する" + "選択したアプリにプロキシを設定する" + "URL/サブネット/ホスト名 PCRE パターン" "ドメイン及び全てのサブドメイン" "プラグイン" -"設定..." +"設定…" "無効" -"不明なプラグイン" +"不明なプラグイン %s" "警告:このプラグインは信頼されていないソースからの可能性があります" -"プラグイン" +"プラグイン: %s" "QRコードをスキャンするにはカメラ権限が必要です。" "VPNサービス" -"NATサービス" - \ No newline at end of file +"手動設定" +"アドバンス" +"サービスモード" +"プロキシのみ" +"VPN" +"トランスプロキシ" +"SOCKS5プロキシポート" +"ローカルDNSポート" +"トランスプロキシポート" +"プロキシサービス" +"トランスプロキシサービス" +"VPNサービス作成のアクセス許可が拒否されました" +"起動時にShadowsockを有効。 VPN常時接続の使用をお勧めします" + diff --git a/mobile/src/main/res/values-ko/strings.xml b/mobile/src/main/res/values-ko/strings.xml index 16886cbeb4..1bc22c62e8 100644 --- a/mobile/src/main/res/values-ko/strings.xml +++ b/mobile/src/main/res/values-ko/strings.xml @@ -1,15 +1,19 @@ +"Shadowsocks" "켜기/끄기" "프로필" "다른 프로필로 전환하거나 새 프로필 추가" -"NAT 모드 (지원 중단)" -"VPN 모드 대신 NAT 모드를 사용합니다. 루트 권한이 필요합니다." "원격 DNS" "송신: \t%3$s\t↑\t%1$s/s 수신: \t%4$s\t↓\t%2$s/s" +"%1$s↑\t%2$s↓" +"%1$s/s↑\t%2$s/s↓" + +"Byte" + "인터넷 연결 상태 검사하기" "검사 중…" "성공: 지연시간 %dms" @@ -17,19 +21,27 @@ "인터넷에 연결할 수 없습니다" "오류 코드: #%d" + +"서버 설정" + "프로필 이름" "서버 주소" "원격 포트" -"로컬 포트" "비밀번호" "암호화 방법" + +"기능 설정" + "IPv6 라우팅" "IPv6 트래픽도 원격으로 리다이렉트 합니다" "라우팅 대상" +"GFW List" + "원하는 앱만 프락시 적용하기" + "선택한 앱에만 프락시를 적용합니다. Android 4.x 이하에서는 NAT 모드를 써야 합니다." "활성화" "선택된 앱들만 프록시 적용 제외하기" @@ -45,31 +57,34 @@ "Shadowsocks가 시작되었습니다" "잘못된 서버 이름입니다" "원격 서버에 접속하는 데 실패했습니다" -"경고: NAT 모드는 Android 5.0부터 지원 중단되었습니다" -"NAT 모드는 루트 권한이 필요합니다" -"VPN 모드로 전환" "중지" "종료 중…" +"%s" "VPN 서비스를 시작하는 데 실패했습니다. 장치를 재시작해 보세요." "올바른 프로필 데이터를 찾을 수 없습니다" + +"예" +"아니오" + "닫기" "프로필을 선택해 주세요" "서버 주소와 비밀번호는 반드시 입력해야 합니다" "접속" -"재설정 중…" "'%s' 프로필을 삭제 하시겠습니까?" "프로필" "설정" "자주 묻는 질문" -"재설정" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "이 앱에 대하여" +"Shadowsocks %s" "수정" "공유" "프로필 추가" +"Apply Settings to All Profiles" "클립보드로 내보내기" "클립보드에서 불러오기" "성공적으로 내보냈습니다" @@ -79,27 +94,46 @@ "프로필 설정" +"변경 사항이 저장되지 않았습니다. 저장하시겠습니까?" +"적용" "삭제" "정말 이 프로필을 삭제하시겠습니까?" "QR 코드/NFC" "이 Shadowsocks 프로필을 추가하시겠습니까?" "QR 코드 읽기" -"손수 써넣기" "삭제했습니다" "실행 취소" + +"Start the service" +"Connect to the current server" +"Connect to %s" +"Switch to %s" +"Use the current profile" + "송신:" "수신:" "연결 중…" "연결 완료. 탭 하면 연결 상태를 검사합니다." -"연결 완료" +"Not connected" "사용자 정의 규칙" +"Selection…" +"Add rule(s)…" +"Edit rule" +"All" +"Bypass LAN" +"Bypass mainland China" +"Bypass LAN & mainland China" +"China List" +"Configure VPN mode for selected apps" + "URL, 서브넷 혹은 호스트 이름 PCRE 패턴" +"Domain name and all its subdomain names" "플러그인" @@ -109,4 +143,21 @@ "경고: 이 플러그인은 신뢰할 수 있는 출처에서 온 것이 아닌 것 같습니다" "플러그인: %s" "QR 코드를 읽어 들이려면 카메라 권한이 필요합니다" - \ No newline at end of file + + +"VPN Service" +"Manual Settings" +"Advanced" +"Service mode" +"Proxy only" +"VPN" +"Transproxy" +"SOCKS5 proxy port" +"Local DNS port" +"Transproxy port" +"Proxy Service" +"Transproxy Service" +"Permission denied to create a VPN service" +"Enable Shadowsocks on startup. Recommended to use always-on VPN + instead" + diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml index 6b9d8fcf64..dca04f20ec 100644 --- a/mobile/src/main/res/values-ru/strings.xml +++ b/mobile/src/main/res/values-ru/strings.xml @@ -1,12 +1,11 @@ +"Shadowsocks" "Подключение" "Профиль" "Переключить на другой профиль или добавить новые" -"Режим NAT (не рекомендуется)" -"Использовать режим NAT вместо VPN. Требует ROOT прав." "Удалённый DNS" "Отправлено: \t%3$s\t↑\t%1$s/s Получено: \t%4$s\t↓\t%2$s/s" @@ -26,20 +25,27 @@ "Интернет недоступен" "Код ошибки: #%d" + +"Настройки Сервера" + "Имя Профиля" "Сервер" "Удалённый порт" -"Локальный порт" "Пароль" "Метод Шифрования" + +"Дополнительные Настройки" + "IPv6 Маршрут" "Перенаправлять трафик IPv6 на удалённый сервер" "Маршрут" "Список GFW" + "Прокси для выбранных приложений" + "Установить прокси для выбранных приложений, требует режим NAT под Android 4.x" "Вкл" "В обход прокси" @@ -55,25 +61,28 @@ "Shadowsocks запущен." "Неправильное имя сервера" "Ошибка при подключении к удалённому серверу" -"ВНИМАНИЕ: режим NAT не рекомендуется, начиная с Android 5.0" -"Режим NAT требует наличия ROOT прав" -"Переключить на режим VPN" "Остановить" "Останавливается…" +"%s" "Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство." +"No valid profile data found." + + +"Да" +"Нет" "Закрыть" "Пожалуйста, выберите профиль" "Прокси/Пароль не должны быть пустыми" "Подключить" -"Сброс…" "Удалить этот профиль %s?" "Профили" "Настройки" -"Сброс" +"FAQ" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "О приложении" "Shadowsocks %s" "Изменить" @@ -89,14 +98,15 @@ "Настройка профиля" +"Изменения не сохранены. Сохранить?" +"Применить" "Удалить" "Вы уверены, что хотите удалить этот профиль?" "QR-код/NFC" "Добавить этот Профиль Shadowsocks?" "Сканировать QR-код" -"Ручные настройки" -"Удалено" +"Удалено %d элементов" "Удалено %d элемента" "Удалено %d элементов" "Удалено %d элементов" @@ -113,31 +123,49 @@ "Отправлено:" "Получено:" -"Соединение..." +"Соединение…" "Подключено, нажмите для проверки соединения" -"Подключено" "Не подключено" "Пользовательские правила" -"Выделение..." -"Добавить правило..." +"Выделение…" +"Добавить правило…" "Редактировать правило" "Все" "Все, кроме LAN" "Все, кроме Китая" "Все, кроме LAN и Китая" "Список Китай" + "Установить прокси для выбранных приложений" + "URL/Подсеть/Регулярное выражение (PCRE) имени хоста" "Доменное имя и все его поддомены" "Плагин" -"Настроить..." +"Настроить…" "Отключён" "Неизвестный плагин %s" "Предупреждение: этот плагин получен из недоверенного источника." "Плагин: %s" "Разрешение камеры требуется для сканирования QR код." - \ No newline at end of file + + +"VPN Service" +"Manual Settings" +"Advanced" +"Service mode" +"Proxy only" +"VPN" +"Transproxy" +"SOCKS5 proxy port" +"Local DNS port" +"Transproxy port" +"Proxy Service" +"Transproxy Service" +"Permission denied to create a VPN service" +"Enable Shadowsocks on startup. Recommended to use always-on VPN + instead" + diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 1c7bb221fa..904743d897 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -1,77 +1,93 @@ + "影梭" + "开关" "配置文件" + "新建或切换到其他配置文件" -"NAT 模式(仅限调试)" -"从 VPN 模式切换为 NAT 模式,需要 ROOT 权限" + "远程 DNS" -"发送: \t%3$s\t↑\t%1$s/s -接收: \t%4$s\t↓\t%2$s/s" + +"上传: \t%3$s\t↑\t%1$s/s +下载: \t%4$s\t↓\t%2$s/s" +"1$s↑\t%2$s↓" +"1$s/s↑\t%2$s/s↓" + "字节" + "检查网络连接" "测试中…" -"连接有效:延时 %d 毫秒" +"连接成功:延时 %d 毫秒" + "失败:%s" "无互联网连接" + "状态码无效(#%d)" + +"服务器设置" + "配置名称" + "服务器" "远程端口" -"本地端口" "密码" -"加密方法" +"加密方式" + + +"功能设置" "IPv6 路由" -"向远程服务器转发 IPv6 流量" +"转发 IPv6 流量到远程服务器" "路由" -"仅代理中国大陆无法访问的地址" -"分应用代理" -"为应用程序分别设置代理,在 4.x 下需要启用 NAT 模式" -"开" +"GFW 列表" +"分应用 VPN" + +"允许部分应用绕过 VPN" +"启用" "绕行模式" -"启用该选项,以使所选应用程序的流量不经过代理" +"绕过选择的应用" "自动连接" -"随系统启动后台服务" +"允许 Shadowsocks 随系统启动" "切换需要 ROOT 权限" -"不支持的内核版本:%s < 3.7.1" +"不支持的内核版本: %s < 3.7.1" "DNS 转发" -"通过 UDP 将所有 DNS 查询全部转发至远程服务器" +"转发所有 DNS 请求到远程服务器" "后台服务已开始运行。" "服务器名无效" "无法连接远程服务器" -"警告:在 Android 5.0 及更高版本中不建议使用 NAT 模式" -"NAT 模式需要 ROOT 权限" -"切换到 VPN 模式" "停止" "正在关闭…" "后台服务启动失败:%s" "VPN 服务启动失败。你可能需要重启设备。" "未找到有效的配置文件。" + +"是" +"否" + "关闭" "请选择配置文件" "代理服务器地址及密码不能为空" "连接" -"重置中…" "删除此配置文件“%s”?" "配置文件" "设置选项" "常见问题" -"重置" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "关于" "影梭 (Shadowsocks) %s" "编辑" @@ -87,12 +103,13 @@ "配置文件设置" +"保存修改吗?" +"应用" "删除" "您确定要删除此配置文件?" "二维码 / NFC" "为影梭添加此配置文件?" "扫描二维码" -"手动设置" "已删除 %d 项" @@ -110,7 +127,6 @@ "接收:" "连接中…" "已连接,点击测试连接" -"已连接" "未连接" @@ -123,7 +139,9 @@ "绕过中国大陆地址" "绕过局域网及中国大陆地址" "仅代理中国大陆地址" + "为应用程序分别设置代理" + "URL/子网/域名 PCRE 正则表达式" "域名及其子域名" @@ -138,5 +156,17 @@ "VPN 服务" -"NAT 服务" - \ No newline at end of file +"手动设置" +"高级选项" +"服务模式" +"仅代理" +"VPN" +"透明代理" +"SOCKS5 代理端口" +"本地 DNS 端口" +"透明代理端口" +"代理模式" +"透明代理模式" +"创建 VPN 服务权限不足" +"允许 Shadowsocks 随系统启动,建议使用始终开启的 VPN" + diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index 9e52352130..7adabe7415 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -1,36 +1,47 @@ +"影梭" "切換" "設定檔" "切換至其他設定檔或新增新設定檔" -"NAT 模式 (已過時)" -"使用 NAT 模式代替 VPN 模式,需要 ROOT 權限" "遠程 DNS" "傳送: \t%3$s\t↑\t%1$s/s 接收: \t%4$s\t↓\t%2$s/s" +"%1$s↑\t%2$s↓" +"%1$s/s↑\t%2$s/s↓" + +"位元组" + "檢查連線能力" "測試中……" "成功: %d 毫秒延遲" "偵測出網際網路連線失敗: %s" "無法使用網際網路" -"錯誤碼: #%d" +"錯誤碼: (#%d)" + + +"伺服器設定" "設定檔名稱" "伺服器" "遠端連接埠" -"本機連接埠" "密碼" "加密方法" + +"功能設定" + "IPv6 路由" "向遠端重新導向 IPv6 流量" "路由" "GFW List" + "個別應用程式的 Proxy" + "為已選擇的應用程式設定 Proxy,在 Android 4.X 以下需要開啟 NAT 模式" "開" "略過模式" @@ -46,28 +57,28 @@ "Shadowsocks 已啟動。" "伺服器名稱無效" "連線至遠端伺服器失敗" -"警告:自 Android 5.0 開始, NAT 模式已過時" -"NAT 模式需要 ROOT 權限" -"切換至 VPN 模式" "停止" "關閉中…" "%s" "VPN 服務啟動失敗。您或許需要重新啟動您的裝置。" "未找到有效的設定檔資料。" + +"是" +"否" + "關閉" "請選擇設定檔" "Proxy 或密碼不可以空白" "連線" -"重設中…" "移除此設定檔 %s?" "設定檔" "設定" "常見問題" -"重設" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "關於" "Shadowsocks %s" "編輯" @@ -83,12 +94,13 @@ "設定檔設定" +"要儲存變更嗎?" +"套用" "刪除" "您確定要移除這個設定檔嗎?" "QR 碼 / NFC" "為 Shadowsocks 新增此設定檔?" "掃描 QR 碼" -"手動設定" "已移除 %d 項" @@ -106,7 +118,6 @@ "接收:" "連線中…" "已連線,輕觸以檢查連線能力" -"已連線" "未連線" @@ -119,13 +130,15 @@ "略過中國大陸" "略過區域網路及中國大陸" "China List" + "為已選擇的應用程式設定 Proxy" + "URL/子網路/主機名稱 PCRE 模式" "網域及其所有子網域" "外掛程式" -"設定..." +"設定…" "停用" "未知插件 %s" "警告:此外掛程式似乎不是來自一個已知的受信任來源。" @@ -134,5 +147,17 @@ "VPN 服務" -"NAT 服務" - \ No newline at end of file +"手動設置" +"高級" +"服務模式" +"仅代理" +"VPN" +"透明代理" +"SOCKS5 代理連接埠" +"本地 DNS 連接埠" +"透明代理連接埠" +"代理服務" +"透明代理服務" +"没有權限創建 VPN 服務" +"允許 Shadowsocks 隨系統啟動,建議使用始終開啟的 VPN" + From d68ccedf329f3739b4024fe1009eb2864192985b Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 22 Nov 2017 01:57:00 -0800 Subject: [PATCH 58/79] Update overture --- mobile/src/overture/src/github.com/shadowsocks/overture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/overture/src/github.com/shadowsocks/overture b/mobile/src/overture/src/github.com/shadowsocks/overture index 943e9dcb62..c8ec480cf7 160000 --- a/mobile/src/overture/src/github.com/shadowsocks/overture +++ b/mobile/src/overture/src/github.com/shadowsocks/overture @@ -1 +1 @@ -Subproject commit 943e9dcb62c47e9016b39185c4d92592e05b6fbb +Subproject commit c8ec480cf716be70bc3cae2c4cd7f2733901ab09 From 47c2c8ea9a6c040d3731e8c80cfd14754dd8c627 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 22 Nov 2017 21:53:42 -0800 Subject: [PATCH 59/79] Refine the layout of profile item --- mobile/src/main/res/layout/layout_profile.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 3111d19108..9e2274b983 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -8,16 +8,14 @@ android:background="@drawable/background_profile" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="8dp" - android:paddingRight="4dp" - android:paddingTop="8dp" - android:paddingBottom="8dp" android:focusable="true" android:nextFocusRight="@+id/edit"> + android:paddingLeft="24dp" + android:paddingRight="4dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp"> From b0fb43c4dbd9b2d9b2307b62f20d34a53c25f188 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 24 Nov 2017 10:38:44 -0800 Subject: [PATCH 60/79] Add a cool badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e173832b7..7a317945ed 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ ## Shadowsocks for Android [![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) +[![Releases](https://img.shields.io/github/downloads/shadowsocks/shadowsocks-android/total.svg)](https://github.com/shadowsocks/shadowsocks-android/releases) A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. - or [releases](https://github.com/shadowsocks/shadowsocks-android/releases). + ### PREREQUISITES From 4168bd65d2cf079d2659f01c3ccc2b5c7d6701cd Mon Sep 17 00:00:00 2001 From: Syrone Wong Date: Tue, 28 Nov 2017 08:42:12 +0800 Subject: [PATCH 61/79] minor improvements starting from NDK r16 (#1458) Signed-off-by: Syrone Wong --- .travis.yml | 2 +- README.md | 2 +- mobile/src/main/jni/Android.mk | 133 +++++++++++++++++++++++------ mobile/src/main/jni/Application.mk | 2 +- 4 files changed, 110 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index cb373aa2df..b7e12830ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jdk: env: global: - - NDK_VERSION=r15b + - NDK_VERSION=r16 - NDK_CCACHE=ccache - GOROOT_BOOTSTRAP=$GOROOT - ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION} diff --git a/README.md b/README.md index 7a317945ed..9486b767f4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * Android SDK - Build Tools 26+ - Android Support Repository and Google Repository (see `build.sbt` for version) - - Android NDK r15+ + - Android NDK r16+ ### BUILD diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index ee7c03c32b..4957b3a672 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -27,52 +27,133 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ + crypto_auth/crypto_auth.c \ + crypto_auth/hmacsha256/auth_hmacsha256.c \ + crypto_auth/hmacsha512/auth_hmacsha512.c \ + crypto_auth/hmacsha512256/auth_hmacsha512256.c \ + crypto_box/crypto_box.c \ + crypto_box/crypto_box_easy.c \ + crypto_box/crypto_box_seal.c \ + crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \ + crypto_core/curve25519/ref10/curve25519_ref10.c \ crypto_core/hchacha20/core_hchacha20.c \ + crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \ + crypto_core/hsalsa20/core_hsalsa20.c \ crypto_core/salsa/ref/core_salsa_ref.c \ + crypto_generichash/crypto_generichash.c \ + crypto_generichash/blake2b/generichash_blake2.c \ crypto_generichash/blake2b/ref/blake2b-compress-ref.c \ crypto_generichash/blake2b/ref/blake2b-ref.c \ crypto_generichash/blake2b/ref/generichash_blake2b.c \ + crypto_hash/crypto_hash.c \ + crypto_hash/sha256/hash_sha256.c \ + crypto_hash/sha256/cp/hash_sha256_cp.c \ + crypto_hash/sha512/hash_sha512.c \ + crypto_hash/sha512/cp/hash_sha512_cp.c \ + crypto_kdf/blake2b/kdf_blake2b.c \ + crypto_kdf/crypto_kdf.c \ + crypto_kx/crypto_kx.c \ + crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ - crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ - crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ crypto_pwhash/argon2/argon2-fill-block-ref.c \ + crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/pwhash_argon2i.c \ + crypto_pwhash/argon2/pwhash_argon2id.c \ + crypto_pwhash/crypto_pwhash.c \ + crypto_scalarmult/crypto_scalarmult.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \ + crypto_secretbox/crypto_secretbox.c \ + crypto_secretbox/crypto_secretbox_easy.c \ + crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \ + crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c \ + crypto_shorthash/crypto_shorthash.c \ + crypto_shorthash/siphash24/shorthash_siphash24.c \ + crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c \ + crypto_sign/crypto_sign.c \ + crypto_sign/ed25519/sign_ed25519.c \ + crypto_sign/ed25519/ref10/keypair.c \ + crypto_sign/ed25519/ref10/open.c \ + crypto_sign/ed25519/ref10/sign.c \ crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/ref/chacha20_ref.c \ + crypto_stream/crypto_stream.c \ crypto_stream/salsa20/stream_salsa20.c \ - crypto_stream/salsa20/ref/salsa20_ref.c \ + crypto_stream/xsalsa20/stream_xsalsa20.c \ crypto_verify/sodium/verify.c \ randombytes/randombytes.c \ - randombytes/sysrandom/randombytes_sysrandom.c \ + sodium/codecs.c \ sodium/core.c \ sodium/runtime.c \ sodium/utils.c \ - sodium/version.c + sodium/version.c \ + randombytes/salsa20/randombytes_salsa20_random.c \ + randombytes/sysrandom/randombytes_sysrandom.c \ + crypto_scalarmult/curve25519/ref10/x25519_ref10.c \ + crypto_stream/salsa20/ref/salsa20_ref.c \ + crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c \ + crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c \ + crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ + crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ + crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ + crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ + crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ + crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c \ + crypto_shorthash/siphash24/shorthash_siphashx24.c \ + crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c \ + crypto_sign/ed25519/ref10/obsolete.c \ + crypto_stream/salsa2012/ref/stream_salsa2012_ref.c \ + crypto_stream/salsa2012/stream_salsa2012.c \ + crypto_stream/salsa208/ref/stream_salsa208_ref.c \ + crypto_stream/salsa208/stream_salsa208.c \ + crypto_stream/xchacha20/stream_xchacha20.c LOCAL_MODULE := sodium -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ +LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/include \ -I$(LOCAL_PATH)/include/sodium \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \ - -DPACKAGE_VERSION=\"1.0.7\" -DPACKAGE_STRING=\"libsodium\ 1.0.7\" \ + -DPACKAGE_VERSION=\"1.0.15\" -DPACKAGE_STRING=\"libsodium\ 1.0.15\" \ -DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \ -DPACKAGE_URL=\"https://github.com/jedisct1/libsodium\" \ - -DPACKAGE=\"libsodium\" -DVERSION=\"1.0.7\" -DSTDC_HEADERS=1 \ - -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 \ - -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 \ - -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 \ - -D__EXTENSIONS__=1 -D_ALL_SOURCE=1 -D_GNU_SOURCE=1 \ - -D_POSIX_PTHREAD_SEMANTICS=1 -D_TANDEM_SOURCE=1 \ - -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" \ - -DHAVE_SYS_MMAN_H=1 -DNATIVE_LITTLE_ENDIAN=1 \ - -DHAVE_WEAK_SYMBOLS=1 -DHAVE_ARC4RANDOM=1 -DHAVE_ARC4RANDOM_BUF=1 \ - -DHAVE_MLOCK=1 -DHAVE_MPROTECT=1 -DHAVE_POSIX_MEMALIGN=1 + -DPACKAGE=\"libsodium\" -DVERSION=\"1.0.15\" \ + -DHAVE_PTHREAD=1 \ + -DSTDC_HEADERS=1 \ + -DHAVE_SYS_TYPES_H=1 \ + -DHAVE_SYS_STAT_H=1 \ + -DHAVE_STDLIB_H=1 \ + -DHAVE_STRING_H=1 \ + -DHAVE_MEMORY_H=1 \ + -DHAVE_STRINGS_H=1 \ + -DHAVE_INTTYPES_H=1 \ + -DHAVE_STDINT_H=1 \ + -DHAVE_UNISTD_H=1 \ + -D__EXTENSIONS__=1 \ + -D_ALL_SOURCE=1 \ + -D_GNU_SOURCE=1 \ + -D_POSIX_PTHREAD_SEMANTICS=1 \ + -D_TANDEM_SOURCE=1 \ + -DHAVE_DLFCN_H=1 \ + -DLT_OBJDIR=\".libs/\" \ + -DHAVE_SYS_MMAN_H=1 \ + -DNATIVE_LITTLE_ENDIAN=1 \ + -DASM_HIDE_SYMBOL=.hidden \ + -DHAVE_WEAK_SYMBOLS=1 \ + -DHAVE_ATOMIC_OPS=1 \ + -DHAVE_ARC4RANDOM=1 \ + -DHAVE_ARC4RANDOM_BUF=1 \ + -DHAVE_MMAP=1 \ + -DHAVE_MLOCK=1 \ + -DHAVE_MADVISE=1 \ + -DHAVE_MPROTECT=1 \ + -DHAVE_NANOSLEEP=1 \ + -DHAVE_POSIX_MEMALIGN=1 \ + -DHAVE_GETPID=1 \ + -DCONFIGURED=1 LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE)) @@ -91,7 +172,7 @@ LIBEVENT_SOURCES := \ LOCAL_MODULE := event LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) -LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libevent \ +LOCAL_CFLAGS := -I$(LOCAL_PATH)/libevent \ -I$(LOCAL_PATH)/libevent/include \ include $(BUILD_STATIC_LIBRARY) @@ -105,7 +186,7 @@ include $(CLEAR_VARS) ANCILLARY_SOURCE := fd_recv.c fd_send.c LOCAL_MODULE := libancillary -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libancillary +LOCAL_CFLAGS += -I$(LOCAL_PATH)/libancillary LOCAL_SRC_FILES := $(addprefix libancillary/, $(ANCILLARY_SOURCE)) @@ -120,7 +201,7 @@ include $(CLEAR_VARS) BLOOM_SOURCE := bloom.c murmur2/MurmurHash2.c LOCAL_MODULE := libbloom -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libbloom \ +LOCAL_CFLAGS += -I$(LOCAL_PATH)/shadowsocks-libev/libbloom \ -I$(LOCAL_PATH)/shadowsocks-libev/libbloom/murmur2 LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libbloom/, $(BLOOM_SOURCE)) @@ -143,7 +224,7 @@ set_src = set/allocation.c set/inspection.c set/ipv4_set.c set/ipv6_set.c \ IPSET_SOURCE := general.c $(bdd_src) $(map_src) $(set_src) LOCAL_MODULE := libipset -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include \ +LOCAL_CFLAGS += -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libipset/src/libipset/,$(IPSET_SOURCE)) @@ -170,7 +251,7 @@ pthreads_src := pthreads/thread.c CORK_SOURCE := $(cli_src) $(core_src) $(ds_src) $(posix_src) $(pthreads_src) LOCAL_MODULE := libcork -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ +LOCAL_CFLAGS += -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ -DCORK_API=CORK_LOCAL LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libcork/src/libcork/,$(CORK_SOURCE)) @@ -184,7 +265,7 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libev -LOCAL_CFLAGS += -O2 -DNDEBUG -DHAVE_CONFIG_H \ +LOCAL_CFLAGS += -DNDEBUG -DHAVE_CONFIG_H \ -I$(LOCAL_PATH)/include/libev LOCAL_SRC_FILES := \ libev/ev.c \ @@ -207,7 +288,7 @@ LOCAL_STATIC_LIBRARIES := libevent LOCAL_MODULE := redsocks LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES)) -LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ +LOCAL_CFLAGS := -std=gnu99 -DUSE_IPTABLES \ -I$(LOCAL_PATH)/redsocks \ -I$(LOCAL_PATH)/libevent/include \ -I$(LOCAL_PATH)/libevent @@ -229,7 +310,7 @@ SHADOWSOCKS_SOURCES := local.c \ LOCAL_MODULE := ss-local LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) -LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \ +LOCAL_CFLAGS := -Wall -fno-strict-aliasing -DMODULE_LOCAL \ -DUSE_CRYPTO_MBEDTLS -DHAVE_CONFIG_H \ -DCONNECT_IN_PROGRESS=EINPROGRESS \ -I$(LOCAL_PATH)/include/shadowsocks-libev \ @@ -265,7 +346,7 @@ SHADOWSOCKS_SOURCES := tunnel.c \ LOCAL_MODULE := ss-tunnel LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) -LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ +LOCAL_CFLAGS := -Wall -fno-strict-aliasing -DMODULE_TUNNEL \ -DUSE_CRYPTO_MBEDTLS -DHAVE_CONFIG_H -DSSTUNNEL_JNI \ -DCONNECT_IN_PROGRESS=EINPROGRESS \ -I$(LOCAL_PATH)/libancillary \ diff --git a/mobile/src/main/jni/Application.mk b/mobile/src/main/jni/Application.mk index e38747eba1..f80bef09c9 100644 --- a/mobile/src/main/jni/Application.mk +++ b/mobile/src/main/jni/Application.mk @@ -1,4 +1,4 @@ APP_ABI := armeabi-v7a arm64-v8a x86 APP_PLATFORM := android-19 -APP_STL := stlport_static +APP_STL := c++_static NDK_TOOLCHAIN_VERSION := clang From 398e0f4d01f5e1f5b762228dc875db7eaac44519 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 27 Nov 2017 18:18:27 -0800 Subject: [PATCH 62/79] Revert to minimal libsodium build --- .travis.yml | 1 + mobile/src/main/jni/Android.mk | 67 +++------------------------------- 2 files changed, 6 insertions(+), 62 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7e12830ee..2f36b48f7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,4 +50,5 @@ install: fi script: + - ccache -C - $SBTPATH/sbt go-build android:package diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 4957b3a672..07670324bf 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -27,89 +27,32 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ - crypto_auth/crypto_auth.c \ - crypto_auth/hmacsha256/auth_hmacsha256.c \ - crypto_auth/hmacsha512/auth_hmacsha512.c \ - crypto_auth/hmacsha512256/auth_hmacsha512256.c \ - crypto_box/crypto_box.c \ - crypto_box/crypto_box_easy.c \ - crypto_box/crypto_box_seal.c \ - crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \ - crypto_core/curve25519/ref10/curve25519_ref10.c \ crypto_core/hchacha20/core_hchacha20.c \ - crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \ - crypto_core/hsalsa20/core_hsalsa20.c \ crypto_core/salsa/ref/core_salsa_ref.c \ - crypto_generichash/crypto_generichash.c \ - crypto_generichash/blake2b/generichash_blake2.c \ crypto_generichash/blake2b/ref/blake2b-compress-ref.c \ crypto_generichash/blake2b/ref/blake2b-ref.c \ crypto_generichash/blake2b/ref/generichash_blake2b.c \ - crypto_hash/crypto_hash.c \ - crypto_hash/sha256/hash_sha256.c \ - crypto_hash/sha256/cp/hash_sha256_cp.c \ - crypto_hash/sha512/hash_sha512.c \ - crypto_hash/sha512/cp/hash_sha512_cp.c \ - crypto_kdf/blake2b/kdf_blake2b.c \ - crypto_kdf/crypto_kdf.c \ - crypto_kx/crypto_kx.c \ - crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ + crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ + crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ crypto_pwhash/argon2/argon2-fill-block-ref.c \ - crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/pwhash_argon2i.c \ - crypto_pwhash/argon2/pwhash_argon2id.c \ - crypto_pwhash/crypto_pwhash.c \ - crypto_scalarmult/crypto_scalarmult.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \ - crypto_secretbox/crypto_secretbox.c \ - crypto_secretbox/crypto_secretbox_easy.c \ - crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \ - crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c \ - crypto_shorthash/crypto_shorthash.c \ - crypto_shorthash/siphash24/shorthash_siphash24.c \ - crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c \ - crypto_sign/crypto_sign.c \ - crypto_sign/ed25519/sign_ed25519.c \ - crypto_sign/ed25519/ref10/keypair.c \ - crypto_sign/ed25519/ref10/open.c \ - crypto_sign/ed25519/ref10/sign.c \ crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/ref/chacha20_ref.c \ - crypto_stream/crypto_stream.c \ crypto_stream/salsa20/stream_salsa20.c \ - crypto_stream/xsalsa20/stream_xsalsa20.c \ + crypto_stream/salsa20/ref/salsa20_ref.c \ crypto_verify/sodium/verify.c \ randombytes/randombytes.c \ - sodium/codecs.c \ + randombytes/sysrandom/randombytes_sysrandom.c \ sodium/core.c \ sodium/runtime.c \ sodium/utils.c \ - sodium/version.c \ - randombytes/salsa20/randombytes_salsa20_random.c \ - randombytes/sysrandom/randombytes_sysrandom.c \ - crypto_scalarmult/curve25519/ref10/x25519_ref10.c \ - crypto_stream/salsa20/ref/salsa20_ref.c \ - crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c \ - crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c \ - crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ - crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ - crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ - crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ - crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ - crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c \ - crypto_shorthash/siphash24/shorthash_siphashx24.c \ - crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c \ - crypto_sign/ed25519/ref10/obsolete.c \ - crypto_stream/salsa2012/ref/stream_salsa2012_ref.c \ - crypto_stream/salsa2012/stream_salsa2012.c \ - crypto_stream/salsa208/ref/stream_salsa208_ref.c \ - crypto_stream/salsa208/stream_salsa208.c \ - crypto_stream/xchacha20/stream_xchacha20.c + sodium/version.c LOCAL_MODULE := sodium LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ From 5abb9061c44139470ab753c08de9a57cb837d125 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 27 Nov 2017 18:31:30 -0800 Subject: [PATCH 63/79] Revert "Revert to minimal libsodium build" This reverts commit 398e0f4d01f5e1f5b762228dc875db7eaac44519. --- .travis.yml | 1 - mobile/src/main/jni/Android.mk | 67 +++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f36b48f7c..b7e12830ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,5 +50,4 @@ install: fi script: - - ccache -C - $SBTPATH/sbt go-build android:package diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 07670324bf..4957b3a672 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -27,32 +27,89 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ + crypto_auth/crypto_auth.c \ + crypto_auth/hmacsha256/auth_hmacsha256.c \ + crypto_auth/hmacsha512/auth_hmacsha512.c \ + crypto_auth/hmacsha512256/auth_hmacsha512256.c \ + crypto_box/crypto_box.c \ + crypto_box/crypto_box_easy.c \ + crypto_box/crypto_box_seal.c \ + crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \ + crypto_core/curve25519/ref10/curve25519_ref10.c \ crypto_core/hchacha20/core_hchacha20.c \ + crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \ + crypto_core/hsalsa20/core_hsalsa20.c \ crypto_core/salsa/ref/core_salsa_ref.c \ + crypto_generichash/crypto_generichash.c \ + crypto_generichash/blake2b/generichash_blake2.c \ crypto_generichash/blake2b/ref/blake2b-compress-ref.c \ crypto_generichash/blake2b/ref/blake2b-ref.c \ crypto_generichash/blake2b/ref/generichash_blake2b.c \ + crypto_hash/crypto_hash.c \ + crypto_hash/sha256/hash_sha256.c \ + crypto_hash/sha256/cp/hash_sha256_cp.c \ + crypto_hash/sha512/hash_sha512.c \ + crypto_hash/sha512/cp/hash_sha512_cp.c \ + crypto_kdf/blake2b/kdf_blake2b.c \ + crypto_kdf/crypto_kdf.c \ + crypto_kx/crypto_kx.c \ + crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ - crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ - crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ crypto_pwhash/argon2/argon2-fill-block-ref.c \ + crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/pwhash_argon2i.c \ + crypto_pwhash/argon2/pwhash_argon2id.c \ + crypto_pwhash/crypto_pwhash.c \ + crypto_scalarmult/crypto_scalarmult.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \ + crypto_secretbox/crypto_secretbox.c \ + crypto_secretbox/crypto_secretbox_easy.c \ + crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \ + crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c \ + crypto_shorthash/crypto_shorthash.c \ + crypto_shorthash/siphash24/shorthash_siphash24.c \ + crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c \ + crypto_sign/crypto_sign.c \ + crypto_sign/ed25519/sign_ed25519.c \ + crypto_sign/ed25519/ref10/keypair.c \ + crypto_sign/ed25519/ref10/open.c \ + crypto_sign/ed25519/ref10/sign.c \ crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/ref/chacha20_ref.c \ + crypto_stream/crypto_stream.c \ crypto_stream/salsa20/stream_salsa20.c \ - crypto_stream/salsa20/ref/salsa20_ref.c \ + crypto_stream/xsalsa20/stream_xsalsa20.c \ crypto_verify/sodium/verify.c \ randombytes/randombytes.c \ - randombytes/sysrandom/randombytes_sysrandom.c \ + sodium/codecs.c \ sodium/core.c \ sodium/runtime.c \ sodium/utils.c \ - sodium/version.c + sodium/version.c \ + randombytes/salsa20/randombytes_salsa20_random.c \ + randombytes/sysrandom/randombytes_sysrandom.c \ + crypto_scalarmult/curve25519/ref10/x25519_ref10.c \ + crypto_stream/salsa20/ref/salsa20_ref.c \ + crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c \ + crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c \ + crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ + crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ + crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ + crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ + crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ + crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c \ + crypto_shorthash/siphash24/shorthash_siphashx24.c \ + crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c \ + crypto_sign/ed25519/ref10/obsolete.c \ + crypto_stream/salsa2012/ref/stream_salsa2012_ref.c \ + crypto_stream/salsa2012/stream_salsa2012.c \ + crypto_stream/salsa208/ref/stream_salsa208_ref.c \ + crypto_stream/salsa208/stream_salsa208.c \ + crypto_stream/xchacha20/stream_xchacha20.c LOCAL_MODULE := sodium LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ From a16aa35d3c21794673310166393565772f93e734 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 28 Nov 2017 20:47:41 -0800 Subject: [PATCH 64/79] Refine remote config --- mobile/google-services.json | 4 ++-- .../scala/com/github/shadowsocks/ShadowsocksApplication.scala | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/google-services.json b/mobile/google-services.json index 0fa9e0cfc9..d3bc57365c 100644 --- a/mobile/google-services.json +++ b/mobile/google-services.json @@ -1,7 +1,7 @@ { "project_info": { "project_number": "261400168171", - "firebase_url": "https://admob-app-id-3330146721.firebaseio.com", + "firebase_url": "https://shadowsocks.azureedge.net", "project_id": "admob-app-id-3330146721", "storage_bucket": "admob-app-id-3330146721.appspot.com" }, @@ -47,4 +47,4 @@ } ], "configuration_version": "1" -} \ No newline at end of file +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 66dd9fa7cf..1fdca816aa 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -152,7 +152,8 @@ class ShadowsocksApplication extends Application { FirebaseApp.initializeApp(this) remoteConfig.setDefaults(R.xml.default_configs) - remoteConfig.fetch().addOnCompleteListener(task => if (task.isSuccessful) remoteConfig.activateFetched()) + remoteConfig.fetch().addOnCompleteListener(task => + if (task.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")) JobManager.create(this).addJobCreator(DonaldTrump) From e1d2511b0bd6e8d3f424ea30100ddd3d7f608ec4 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 29 Nov 2017 22:41:43 -0800 Subject: [PATCH 65/79] Update CDN URLs --- mobile/google-services.json | 2 +- mobile/src/main/res/xml/default_configs.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/google-services.json b/mobile/google-services.json index d3bc57365c..069a88e80b 100644 --- a/mobile/google-services.json +++ b/mobile/google-services.json @@ -1,7 +1,7 @@ { "project_info": { "project_number": "261400168171", - "firebase_url": "https://shadowsocks.azureedge.net", + "firebase_url": "https://admob-app-id-3330146721.firebaseio.com", "project_id": "admob-app-id-3330146721", "storage_bucket": "admob-app-id-3330146721.appspot.com" }, diff --git a/mobile/src/main/res/xml/default_configs.xml b/mobile/src/main/res/xml/default_configs.xml index 2f71ea5e35..c98452dd62 100644 --- a/mobile/src/main/res/xml/default_configs.xml +++ b/mobile/src/main/res/xml/default_configs.xml @@ -2,6 +2,6 @@ proxy_url - https://www.socks123.pw/get.php + https://socks123.azureedge.net/get.php From 0ae15f61d96add5b8239c868ba091981ee0a6873 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 30 Nov 2017 20:38:17 +0800 Subject: [PATCH 66/79] Fix #1469 --- mobile/src/main/res/values-ja/strings.xml | 6 +----- mobile/src/main/res/values-ko/strings.xml | 6 +----- mobile/src/main/res/values-ru/strings.xml | 4 +--- mobile/src/main/res/values-zh-rCN/strings.xml | 2 -- mobile/src/main/res/values-zh-rTW/strings.xml | 2 -- mobile/src/main/res/values/strings.xml | 4 ++-- 6 files changed, 5 insertions(+), 19 deletions(-) diff --git a/mobile/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml index c60609c2a0..02632ef1fb 100644 --- a/mobile/src/main/res/values-ja/strings.xml +++ b/mobile/src/main/res/values-ja/strings.xml @@ -9,11 +9,7 @@ "リモートDNS" "送信済み: \t%3$s\t↑\t%1$s/s 受信済み: \t%4$s\t↓\t%2$s/s" -"%1$s↑\t%2$s↓" -"%1$s/s↑\t%2$s/s↓" - -"Byte" - + "接続状況確認" "テスト中…" "成功: %dmsの遅延" diff --git a/mobile/src/main/res/values-ko/strings.xml b/mobile/src/main/res/values-ko/strings.xml index 1bc22c62e8..ea1c0ffbcf 100644 --- a/mobile/src/main/res/values-ko/strings.xml +++ b/mobile/src/main/res/values-ko/strings.xml @@ -9,11 +9,7 @@ "원격 DNS" "송신: \t%3$s\t↑\t%1$s/s 수신: \t%4$s\t↓\t%2$s/s" -"%1$s↑\t%2$s↓" -"%1$s/s↑\t%2$s/s↓" - -"Byte" - + "인터넷 연결 상태 검사하기" "검사 중…" "성공: 지연시간 %dms" diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml index dca04f20ec..20e46227e7 100644 --- a/mobile/src/main/res/values-ru/strings.xml +++ b/mobile/src/main/res/values-ru/strings.xml @@ -9,9 +9,7 @@ "Удалённый DNS" "Отправлено: \t%3$s\t↑\t%1$s/s Получено: \t%4$s\t↓\t%2$s/s" -" -%1$s↑\t%2$s↓" -"%1$s/s↑\t%2$s/s↓" + "байт" "байта" diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 904743d897..14a4af2bbf 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -14,8 +14,6 @@ "上传: \t%3$s\t↑\t%1$s/s 下载: \t%4$s\t↓\t%2$s/s" -"1$s↑\t%2$s↓" -"1$s/s↑\t%2$s/s↓" "字节" diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index 7adabe7415..d6b7e07e88 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -9,8 +9,6 @@ "遠程 DNS" "傳送: \t%3$s\t↑\t%1$s/s 接收: \t%4$s\t↓\t%2$s/s" -"%1$s↑\t%2$s↓" -"%1$s/s↑\t%2$s/s↓" "位元组" diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 973b61e7c4..dc03ecee6f 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -18,8 +18,8 @@ Remote DNS Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s - %1$s↑\t%2$s↓ - %1$s/s↑\t%2$s/s↓ + %1$s↑\t%2$s↓ + %1$s/s↑\t%2$s/s↓ Byte Bytes From f2b2aba900437d1494dac60d105442954c86900b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 25 Nov 2017 01:18:21 -0800 Subject: [PATCH 67/79] Update dependencies --- build.sbt | 2 +- mobile/build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index d45836736d..9c85937686 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ lazy val commonSettings = Seq( resolvers += "google" at "https://maven.google.com" ) -val supportLibsVersion = "27.0.1" +val supportLibsVersion = "27.0.2" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) diff --git a/mobile/build.sbt b/mobile/build.sbt index b4e45a016b..8bcaab12bb 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -23,11 +23,11 @@ proguardOptions ++= "-keep public class com.evernote.android.job.JobRescheduleService" :: Nil -val playServicesVersion = "11.6.0" +val playServicesVersion = "11.6.2" resolvers += Resolver.jcenterRepo libraryDependencies ++= "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: - "com.evernote" % "android-job" % "1.2.0" :: + "com.evernote" % "android-job" % "1.2.1" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.google.android.gms" % "play-services-ads" % playServicesVersion :: "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: From 74917eac138dafbd74d4d4217d344df309a48f08 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 19:03:37 -0800 Subject: [PATCH 68/79] Refine paddings --- mobile/src/main/res/layout/layout_profile.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 9e2274b983..33d78bfc47 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -14,7 +14,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:paddingLeft="24dp" + android:paddingLeft="16dp" android:paddingRight="4dp" android:id="@+id/container"> + android:layout_marginBottom="16dp"> Date: Thu, 30 Nov 2017 20:10:47 -0800 Subject: [PATCH 69/79] Use Gericop's preference library (#1471) * New SimpleMenuPreference; * More Oreo-like preference style; * I'm tired of fixing all those bugs; * Remove NumberPickerPreference which nobody likes. --- .github/faq.md | 5 +- build.sbt | 9 +- .../github/shadowsocks/DialogFragment.java | 15 ---- mobile/src/main/res/values/colors.xml | 1 + mobile/src/main/res/values/styles.xml | 3 - mobile/src/main/res/xml/pref_global.xml | 30 +++---- mobile/src/main/res/xml/pref_profile.xml | 45 +++++----- .../shadowsocks/GlobalConfigFragment.scala | 6 +- .../com/github/shadowsocks/MainActivity.scala | 13 +-- .../shadowsocks/ProfileConfigActivity.scala | 7 +- .../shadowsocks/ProfileConfigFragment.scala | 15 ++-- .../com/github/shadowsocks/QRCodeDialog.scala | 9 +- .../shadowsocks/QuickToggleShortcut.scala | 3 +- .../github/shadowsocks/ScannerActivity.scala | 5 +- .../shadowsocks/ShadowsocksApplication.scala | 7 +- .../github/shadowsocks/ToolbarFragment.scala | 2 +- .../github/shadowsocks/database/Profile.scala | 6 +- .../BottomSheetPreferenceDialogFragment.scala | 4 +- .../preference/IconListPreference.scala | 6 +- .../OrmLitePreferenceDataStore.scala | 15 +++- .../PluginConfigurationDialogFragment.scala | 15 +++- .../com/github/shadowsocks/utils/Utils.scala | 8 ++ .../res/layout/preference_dialog_edittext.xml | 40 --------- plugin/src/main/res/values-v21/styles.xml | 4 - plugin/src/main/res/values/attrs.xml | 7 -- plugin/src/main/res/values/styles.xml | 38 +------- .../preference/DialogPreferencePlus.scala | 28 ------ .../mygod/preference/EditTextPreference.scala | 67 -------------- .../EditTextPreferenceDialogFragment.scala | 53 ----------- .../preference/NumberPickerPreference.scala | 65 -------------- ...NumberPickerPreferenceDialogFragment.scala | 52 ----------- .../mygod/preference/PreferenceCategory.scala | 38 -------- .../mygod/preference/PreferenceFragment.scala | 55 ------------ .../preference/PreferenceGroupAdapter.scala | 87 ------------------- .../mygod/preference/SummaryPreference.scala | 45 ---------- .../plugin/OptionsCapableActivity.scala | 4 +- 36 files changed, 130 insertions(+), 682 deletions(-) delete mode 100644 mobile/src/main/java/com/github/shadowsocks/DialogFragment.java delete mode 100644 plugin/src/main/res/layout/preference_dialog_edittext.xml delete mode 100644 plugin/src/main/res/values-v21/styles.xml delete mode 100644 plugin/src/main/res/values/attrs.xml delete mode 100644 plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala delete mode 100644 plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala delete mode 100755 plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala diff --git a/.github/faq.md b/.github/faq.md index 742e3df3c3..02ebab233d 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -9,10 +9,9 @@ Cannot connect to server: Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data. -### UI tips +### How to create a widget and/or switch profile based on network connectivity? -* Tap the number to enter the port you wish to use; (if the keyboard doesn't pop up automatically for some reason) -* Use Tasker integration to create a desktop widget. +Use [Tasker](http://tasker.dinglisch.net/) integration. ### How to add QR code from local gallery? diff --git a/build.sbt b/build.sbt index 9c85937686..acb7123012 100644 --- a/build.sbt +++ b/build.sbt @@ -20,10 +20,13 @@ lazy val commonSettings = Seq( resConfigs := Seq("fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"), + resolvers += Resolver.jcenterRepo, + resolvers += Resolver.bintrayRepo("gericop", "maven"), resolvers += "google" at "https://maven.google.com" ) val supportLibsVersion = "27.0.2" +val takisoftFixVersion = "27.0.2.0" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) @@ -34,7 +37,11 @@ run in Android := (run in (mobile, Android)).evaluated lazy val plugin = project .settings(commonSettings) .settings( - libraryDependencies += "com.android.support" % "preference-v14" % supportLibsVersion + libraryDependencies ++= + "com.android.support" % "preference-v14" % supportLibsVersion :: + "com.takisoft.fix" % "preference-v7" % takisoftFixVersion :: + "com.takisoft.fix" % "preference-v7-simplemenu" % takisoftFixVersion :: + Nil ) lazy val mobile = project diff --git a/mobile/src/main/java/com/github/shadowsocks/DialogFragment.java b/mobile/src/main/java/com/github/shadowsocks/DialogFragment.java deleted file mode 100644 index b05b2ade7c..0000000000 --- a/mobile/src/main/java/com/github/shadowsocks/DialogFragment.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.shadowsocks; - -import android.app.Activity; - -/** - * This helper class is used to solve Java-Scala interop problem. Use onAttach(Context) and remove this class - * when minSdkVersion >= 23. - * - * @author Mygod - */ -public class DialogFragment extends android.app.DialogFragment { - protected void superOnAttach(Activity activity) { - super.onAttach(activity); - } -} diff --git a/mobile/src/main/res/values/colors.xml b/mobile/src/main/res/values/colors.xml index 64da4fad67..afa4fe44ba 100644 --- a/mobile/src/main/res/values/colors.xml +++ b/mobile/src/main/res/values/colors.xml @@ -1,4 +1,5 @@ #7488A1 + @color/material_accent_200 diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index 956d9f2dc0..027dd5af91 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -1,8 +1,5 @@ - - - - - - - - - - - diff --git a/plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala b/plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala deleted file mode 100644 index 36b02fc89f..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala +++ /dev/null @@ -1,28 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.app.DialogFragment -import android.support.v7.preference.DialogPreference - -trait DialogPreferencePlus extends DialogPreference { - def createDialog(): DialogFragment -} diff --git a/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala deleted file mode 100644 index a57fb98ddd..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala +++ /dev/null @@ -1,67 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.content.Context -import android.support.v7.preference.{EditTextPreference => Parent} -import android.support.v7.widget.AppCompatEditText -import android.text.InputType -import android.util.AttributeSet -import android.view.ViewGroup -import android.widget.FrameLayout -import com.github.shadowsocks.plugin.R - -/** - * Fixed EditTextPreference + SummaryPreference with password support! - * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference - */ -class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs) - with DialogPreferencePlus with SummaryPreference { - val editText = new AppCompatEditText(context, attrs) - editText.setId(android.R.id.edit) - - { - val arr = context.obtainStyledAttributes(Array(R.attr.dialogPreferredPadding)) - val margin = arr.getDimensionPixelOffset(0, 0) - arr.recycle() - val params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - params.setMargins(margin, 0, margin, 0) - editText.setLayoutParams(params) - } - - override def createDialog() = new EditTextPreferenceDialogFragment() - - override protected def getSummaryValue: String = { - var text = getText - if (text == null) text = "" - val inputType = editText.getInputType - if (inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) || - inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) || - inputType == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)) - "\u2022" * text.length else text - } - - override def setText(text: String): Unit = { - val old = getText - super.setText(text) - if (old != text) notifyChanged() - } -} diff --git a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala deleted file mode 100644 index e8ed19e451..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala +++ /dev/null @@ -1,53 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.support.v14.preference.PreferenceDialogFragment -import android.support.v7.widget.AppCompatEditText -import android.view.{View, ViewGroup} - -class EditTextPreferenceDialogFragment extends PreferenceDialogFragment { - private lazy val preference = getPreference.asInstanceOf[EditTextPreference] - protected lazy val editText: AppCompatEditText = preference.editText - - override protected def onBindDialogView(view: View) { - super.onBindDialogView(view) - editText.setText(preference.getText) - val text = editText.getText - if (text != null) editText.setSelection(0, text.length) - val oldParent = editText.getParent.asInstanceOf[ViewGroup] - if (oldParent eq view) return - if (oldParent != null) oldParent.removeView(editText) - val oldEdit = view.findViewById[View](android.R.id.edit) - if (oldEdit == null) return - val container = oldEdit.getParent.asInstanceOf[ViewGroup] - if (container == null) return - container.removeView(oldEdit) - container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - } - - override protected def needInputMethod = true - - def onDialogClosed(positiveResult: Boolean): Unit = if (positiveResult) { - val value = editText.getText.toString - if (preference.callChangeListener(value)) preference.setText(value) - } -} diff --git a/plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala b/plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala deleted file mode 100644 index 9b3036707f..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala +++ /dev/null @@ -1,65 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.content.Context -import android.content.res.TypedArray -import android.support.v7.preference.DialogPreference -import android.util.AttributeSet -import android.view.ContextThemeWrapper -import android.widget.NumberPicker -import com.github.shadowsocks.plugin.R - -class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null) - extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference { - private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle)) - private var value: Int = _ - - { - val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference) - setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0)) - setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1)) - a.recycle() - } - - override def createDialog() = new NumberPickerPreferenceDialogFragment() - - def getValue: Int = value - def getMin: Int = if (picker == null) 0 else picker.getMinValue - def getMax: Int = picker.getMaxValue - def setValue(i: Int) { - if (i == value) return - picker.setValue(i) - value = picker.getValue - persistInt(value) - notifyChanged() - } - def setMin(value: Int): Unit = picker.setMinValue(value) - def setMax(value: Int): Unit = picker.setMaxValue(value) - - override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef = - a.getInt(index, getMin).asInstanceOf[AnyRef] - override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) { - val default = defaultValue.asInstanceOf[Int] - setValue(if (restorePersistedValue) getPersistedInt(default) else default) - } - protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef] -} diff --git a/plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala deleted file mode 100644 index 5f8f50abca..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala +++ /dev/null @@ -1,52 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.content.Context -import android.support.v14.preference.PreferenceDialogFragment -import android.view.{View, ViewGroup} -import android.widget.NumberPicker - -class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment { - private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference] - private lazy val picker = preference.picker - - override protected def onCreateDialogView(context: Context): NumberPicker = { - val parent = picker.getParent.asInstanceOf[ViewGroup] - if (parent != null) parent.removeView(picker) - picker - } - - override protected def onBindDialogView(view: View) { - super.onBindDialogView(view) - picker.setValue(preference.getValue) - } - - override protected def needInputMethod = true - - def onDialogClosed(positiveResult: Boolean) { - picker.clearFocus() // commit changes - if (positiveResult) { - val value = picker.getValue - if (preference.callChangeListener(value)) preference.setValue(value) - } - } -} diff --git a/plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala deleted file mode 100644 index 4128010ad7..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala +++ /dev/null @@ -1,38 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.content.Context -import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base} -import android.util.AttributeSet -import android.view.ViewGroup.MarginLayoutParams - -/** - * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/4c2bb2896895dbedb37b659b36bd2a96b33c1605/preference-v7/src/main/java/com/takisoft/fix/support/v7/preference/PreferenceCategory.java - * - * @author Mygod - */ -class PreferenceCategory(context: Context, attrs: AttributeSet = null) extends Base(context, attrs) { - override def onBindViewHolder(holder: PreferenceViewHolder) { - super.onBindViewHolder(holder) - holder.findViewById(android.R.id.title).getLayoutParams.asInstanceOf[MarginLayoutParams].bottomMargin = 0 - } -} diff --git a/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala deleted file mode 100644 index 95139398bc..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala +++ /dev/null @@ -1,55 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.app.DialogFragment -import android.os.Bundle -import android.support.v14.preference.{PreferenceFragment => Base} -import android.support.v7.preference.{Preference, PreferenceScreen} -import android.view.{LayoutInflater, View, ViewGroup} - -abstract class PreferenceFragment extends Base { - override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = - super.onCreateView(inflater, container, savedInstanceState) - - protected final def displayPreferenceDialog(key: String, fragment: DialogFragment, other: Bundle = null) { - val bundle = new Bundle(1) - bundle.putString("key", key) - if (other != null) bundle.putAll(other) - fragment.setArguments(bundle) - fragment.setTargetFragment(this, 0) - getFragmentManager.beginTransaction() - .add(fragment, "android.support.v14.preference.PreferenceFragment.DIALOG") - .commitAllowingStateLoss() - } - - override def onDisplayPreferenceDialog(preference: Preference): Unit = preference match { - case dpp: DialogPreferencePlus => displayPreferenceDialog(preference.getKey, dpp.createDialog()) - case _ => super.onDisplayPreferenceDialog(preference) - } - - override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen) - - override def onResume() { - super.onResume() - getListView.scrollBy(0, 0) - } -} diff --git a/plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala deleted file mode 100644 index 8df8b6efe4..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala +++ /dev/null @@ -1,87 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import java.lang.reflect.Field -import java.util - -import android.os.Build -import android.support.v4.content.ContextCompat -import android.support.v4.view.ViewCompat -import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old} -import android.view.{LayoutInflater, View, ViewGroup} -import com.github.shadowsocks.plugin.R - -/** - * Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69 - * - * @author Mygod - */ -object PreferenceGroupAdapter { - private var preferenceLayoutsField: Field = _ - private var fieldResId: Field = _ - private var fieldWidgetResId: Field = _ - private val preferenceViewHolderConstructor = classOf[PreferenceViewHolder].getDeclaredConstructor(classOf[View]) - - { - val oldClass = classOf[Old] - preferenceLayoutsField = oldClass.getDeclaredField("mPreferenceLayouts") - preferenceLayoutsField.setAccessible(true) - val c = oldClass.getDeclaredClasses.filter(c => c.getSimpleName == "PreferenceLayout").head - fieldResId = c.getDeclaredField("resId") - fieldResId.setAccessible(true) - fieldWidgetResId = c.getDeclaredField("widgetResId") - fieldWidgetResId.setAccessible(true) - preferenceViewHolderConstructor.setAccessible(true) - } -} - -class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) { - import PreferenceGroupAdapter._ - - protected lazy val preferenceLayouts: util.List[AnyRef] = - preferenceLayoutsField.get(this).asInstanceOf[util.List[AnyRef]] - - override def onCreateViewHolder(parent: ViewGroup, viewType: Int): PreferenceViewHolder = - if (Build.VERSION.SDK_INT < 21) { - val context = parent.getContext - val inflater = LayoutInflater.from(context) - val pl = preferenceLayouts.get(viewType) - val view = inflater.inflate(fieldResId.get(pl).asInstanceOf[Int], parent, false) - if (view.getBackground == null) { - val array = context.obtainStyledAttributes(null, R.styleable.BackgroundStyle) - var background = array.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground) - if (background == null) - background = ContextCompat.getDrawable(context, android.R.drawable.list_selector_background) - array.recycle() - val (s, t, e, b) = (ViewCompat.getPaddingStart(view), view.getPaddingTop, - ViewCompat.getPaddingEnd(view), view.getPaddingBottom) - view.setBackground(background) - ViewCompat.setPaddingRelative(view, s, t, e, b) - } - val widgetFrame = view.findViewById[ViewGroup](android.R.id.widget_frame) - if (widgetFrame != null) { - val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int] - if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE) - } - preferenceViewHolderConstructor.newInstance(view) - } else super.onCreateViewHolder(parent, viewType) -} diff --git a/plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala b/plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala deleted file mode 100755 index e1ba4e62ed..0000000000 --- a/plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala +++ /dev/null @@ -1,45 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* This program is free software: you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation, either version 3 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program. If not, see . */ -/* */ -/*******************************************************************************/ - -package be.mygod.preference - -import android.support.v7.preference.Preference - -/** - * Make your preference support %s in summary. Override getSummaryValue to customize what to put in. - * Based on: - * https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/ListPreference.java - * - * @author Mygod - */ -trait SummaryPreference extends Preference { - protected def getSummaryValue: AnyRef - - /** - * Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it - * (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place. - * - * @return the summary with appropriate string substitution - */ - override def getSummary: String = { - val summary = super.getSummary - if (summary == null) null else String.format(summary.toString, getSummaryValue) - } -} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala index c2cb0f3952..829e495dbd 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala @@ -1,8 +1,8 @@ package com.github.shadowsocks.plugin -import android.app.Activity import android.content.Intent import android.os.Bundle +import android.support.v7.app.AppCompatActivity import android.widget.Toast /** @@ -10,7 +10,7 @@ import android.widget.Toast * * @author Mygod */ -trait OptionsCapableActivity extends Activity { +trait OptionsCapableActivity extends AppCompatActivity { protected def pluginOptions(intent: Intent = getIntent): PluginOptions = try new PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS)) catch { case exc: IllegalArgumentException => From 4e5c67e8146df5d6b21e498751b1b629b4f201f0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 20:15:32 -0800 Subject: [PATCH 70/79] Move port default value to OrmLitePreferenceDataStore --- .../github/shadowsocks/ShadowsocksApplication.scala | 8 ++------ .../preference/OrmLitePreferenceDataStore.scala | 13 ++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index c7db813245..e9d77c043d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -28,7 +28,7 @@ import android.app.{Application, NotificationChannel, NotificationManager} import android.content._ import android.content.pm.{PackageInfo, PackageManager} import android.content.res.Configuration -import android.os.{Binder, Build, LocaleList} +import android.os.{Build, LocaleList} import android.support.v7.app.AppCompatDelegate import android.util.Log import com.evernote.android.job.JobManager @@ -163,11 +163,7 @@ class ShadowsocksApplication extends Application { TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets() - // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat - lazy val userIndex = Binder.getCallingUserHandle.hashCode - if (!(1025 to 65535 contains dataStore.portProxy)) dataStore.putInt(Key.portProxy, 1080 + userIndex) - if (!(1025 to 65535 contains dataStore.portLocalDns)) dataStore.putInt(Key.portLocalDns, 5450 + userIndex) - if (!(1025 to 65535 contains dataStore.portTransproxy)) dataStore.putInt(Key.portTransproxy, 8200 + userIndex) + (dataStore.portProxy, dataStore.portLocalDns, dataStore.portTransproxy) // initialize ports for UI if (Build.VERSION.SDK_INT >= 26) { val nm = getSystemService(classOf[NotificationManager]) diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala index 404cec1f82..034bbfb870 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala @@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream import java.nio.ByteBuffer import java.util +import android.os.Binder import android.support.v7.preference.PreferenceDataStore import com.github.shadowsocks.database.{DBHelper, KeyValuePair} import com.github.shadowsocks.utils.{Key, Utils} @@ -98,8 +99,10 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener - private def getLocalPort(key: String) = getInt(key, 0) match { - case 0 => Utils.parsePort(getString(key), 0) + // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat + private lazy val userIndex = Binder.getCallingUserHandle.hashCode + private def getLocalPort(key: String, default: Int) = getInt(key, 0) match { + case 0 => Utils.parsePort(getString(key), default + userIndex) case value => putString(key, value.toString) value @@ -108,9 +111,9 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat def profileId: Int = getInt(Key.id, 0) def profileId_=(i: Int): Unit = putInt(Key.id, i) def serviceMode: String = getString(Key.serviceMode, Key.modeVpn) - def portProxy: Int = getLocalPort(Key.portProxy) - def portLocalDns: Int = getLocalPort(Key.portLocalDns) - def portTransproxy: Int = getLocalPort(Key.portTransproxy) + def portProxy: Int = getLocalPort(Key.portProxy, 1080) + def portLocalDns: Int = getLocalPort(Key.portLocalDns, 5450) + def portTransproxy: Int = getLocalPort(Key.portTransproxy, 8200) def proxyApps: Boolean = getBoolean(Key.proxyApps) def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value) From 273eca7bd1e656aebbed0430a6349feafce6d502 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 30 Nov 2017 17:59:40 -0800 Subject: [PATCH 71/79] Update shadowsocks-libev --- mobile/src/main/jni/include/shadowsocks-libev/config.h | 3 +++ mobile/src/main/jni/shadowsocks-libev | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/jni/include/shadowsocks-libev/config.h b/mobile/src/main/jni/include/shadowsocks-libev/config.h index 59451b9683..8594fa0dbe 100644 --- a/mobile/src/main/jni/include/shadowsocks-libev/config.h +++ b/mobile/src/main/jni/include/shadowsocks-libev/config.h @@ -41,6 +41,9 @@ /* Define to 1 if you have the header file. */ #define HAVE_DLFCN_H 1 +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_TCP_H 1 + /* Define to 1 if you have the `epoll_ctl' function. */ /* #undef HAVE_EPOLL_CTL */ diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 666801ddfc..0c3cf8beb3 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 666801ddfcf70ebc7418b238129c9bfaa9325b44 +Subproject commit 0c3cf8beb3a9309603c595a047d89c366d3cd383 From 0171261465df8a1c8984dce44866d46fdf9da172 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:01:23 -0800 Subject: [PATCH 72/79] Fix a crash on Android 4.4 --- .../scala/com/github/shadowsocks/ShadowsocksApplication.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index e9d77c043d..c134d13844 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -93,8 +93,8 @@ class ShadowsocksApplication extends Application { } private def checkChineseLocale(locale: Locale): Locale = if (locale.getLanguage == "zh") locale.getCountry match { - case "CN" | "TW" => null // already supported - case _ => locale.getScript match { // fallback to the corresponding script + case "CN" | "TW" => null + case _ => (if (Build.VERSION.SDK_INT >= 21) locale.getScript else null) match { case "Hans" => SIMPLIFIED_CHINESE case "Hant" => TRADITIONAL_CHINESE case script => From 722ca5b4f48492647485ec6bd3bcbfba1878144b Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:10:32 -0800 Subject: [PATCH 73/79] Handle malformed requests in TaskerActivity --- .../main/scala/com/github/shadowsocks/TaskerActivity.scala | 6 +++++- .../scala/com/github/shadowsocks/utils/TaskerSettings.scala | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala index 944b27e9ec..733ae3e727 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala @@ -82,6 +82,11 @@ class TaskerActivity extends AppCompatActivity { override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) + try taskerOption = TaskerSettings.fromIntent(getIntent) catch { + case _: Exception => + finish() + return + } setContentView(R.layout.layout_tasker) val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar] @@ -89,7 +94,6 @@ class TaskerActivity extends AppCompatActivity { toolbar.setNavigationIcon(R.drawable.ic_navigation_close) toolbar.setNavigationOnClickListener(_ => finish()) - taskerOption = TaskerSettings.fromIntent(getIntent) switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch] switch.setChecked(taskerOption.switchOn) val profilesList = findViewById(R.id.list).asInstanceOf[RecyclerView] diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala index 20ded7c4fb..c256f5a877 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala @@ -30,8 +30,7 @@ object TaskerSettings { private val KEY_SWITCH_ON = "switch_on" private val KEY_PROFILE_ID = "profile_id" - def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE)) - intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY) + def fromIntent(intent: Intent) = new TaskerSettings(intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE)) } class TaskerSettings(bundle: Bundle) { From ae833210c620e80324fb3ae8d50a5ec256ac63ee Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:49:26 -0800 Subject: [PATCH 74/79] Revert to minimal libsodium source fileset --- mobile/src/main/jni/Android.mk | 67 +++------------------------------- 1 file changed, 6 insertions(+), 61 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 4957b3a672..0d19b08864 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -27,89 +27,34 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ - crypto_auth/crypto_auth.c \ - crypto_auth/hmacsha256/auth_hmacsha256.c \ - crypto_auth/hmacsha512/auth_hmacsha512.c \ - crypto_auth/hmacsha512256/auth_hmacsha512256.c \ - crypto_box/crypto_box.c \ - crypto_box/crypto_box_easy.c \ - crypto_box/crypto_box_seal.c \ - crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \ crypto_core/curve25519/ref10/curve25519_ref10.c \ crypto_core/hchacha20/core_hchacha20.c \ - crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \ - crypto_core/hsalsa20/core_hsalsa20.c \ crypto_core/salsa/ref/core_salsa_ref.c \ - crypto_generichash/crypto_generichash.c \ - crypto_generichash/blake2b/generichash_blake2.c \ crypto_generichash/blake2b/ref/blake2b-compress-ref.c \ crypto_generichash/blake2b/ref/blake2b-ref.c \ crypto_generichash/blake2b/ref/generichash_blake2b.c \ - crypto_hash/crypto_hash.c \ - crypto_hash/sha256/hash_sha256.c \ - crypto_hash/sha256/cp/hash_sha256_cp.c \ - crypto_hash/sha512/hash_sha512.c \ - crypto_hash/sha512/cp/hash_sha512_cp.c \ - crypto_kdf/blake2b/kdf_blake2b.c \ - crypto_kdf/crypto_kdf.c \ - crypto_kx/crypto_kx.c \ - crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ + crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ + crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ crypto_pwhash/argon2/argon2-fill-block-ref.c \ - crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/pwhash_argon2i.c \ - crypto_pwhash/argon2/pwhash_argon2id.c \ - crypto_pwhash/crypto_pwhash.c \ - crypto_scalarmult/crypto_scalarmult.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \ - crypto_secretbox/crypto_secretbox.c \ - crypto_secretbox/crypto_secretbox_easy.c \ - crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \ - crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c \ - crypto_shorthash/crypto_shorthash.c \ - crypto_shorthash/siphash24/shorthash_siphash24.c \ - crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c \ - crypto_sign/crypto_sign.c \ - crypto_sign/ed25519/sign_ed25519.c \ - crypto_sign/ed25519/ref10/keypair.c \ - crypto_sign/ed25519/ref10/open.c \ - crypto_sign/ed25519/ref10/sign.c \ + crypto_scalarmult/curve25519/ref10/x25519_ref10.c \ crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/ref/chacha20_ref.c \ - crypto_stream/crypto_stream.c \ crypto_stream/salsa20/stream_salsa20.c \ - crypto_stream/xsalsa20/stream_xsalsa20.c \ + crypto_stream/salsa20/ref/salsa20_ref.c \ crypto_verify/sodium/verify.c \ randombytes/randombytes.c \ - sodium/codecs.c \ + randombytes/sysrandom/randombytes_sysrandom.c \ sodium/core.c \ sodium/runtime.c \ sodium/utils.c \ - sodium/version.c \ - randombytes/salsa20/randombytes_salsa20_random.c \ - randombytes/sysrandom/randombytes_sysrandom.c \ - crypto_scalarmult/curve25519/ref10/x25519_ref10.c \ - crypto_stream/salsa20/ref/salsa20_ref.c \ - crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c \ - crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c \ - crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ - crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ - crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ - crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ - crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ - crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c \ - crypto_shorthash/siphash24/shorthash_siphashx24.c \ - crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c \ - crypto_sign/ed25519/ref10/obsolete.c \ - crypto_stream/salsa2012/ref/stream_salsa2012_ref.c \ - crypto_stream/salsa2012/stream_salsa2012.c \ - crypto_stream/salsa208/ref/stream_salsa208_ref.c \ - crypto_stream/salsa208/stream_salsa208.c \ - crypto_stream/xchacha20/stream_xchacha20.c + sodium/version.c LOCAL_MODULE := sodium LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ From 5c75148813d992e1a38a5c87a9de7863481b76d0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:49:41 -0800 Subject: [PATCH 75/79] Add support for downgrading the database for whatever reason And it simply kills all the data. --- .../shadowsocks/database/DBHelper.scala | 169 +++++++++--------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 880394c70f..f92e1c3dc2 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -27,8 +27,8 @@ import android.content.pm.ApplicationInfo import android.database.sqlite.SQLiteDatabase import android.preference.PreferenceManager import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.utils.Key +import com.j256.ormlite.android.AndroidDatabaseConnection import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper import com.j256.ormlite.dao.Dao import com.j256.ormlite.support.ConnectionSource @@ -68,96 +68,99 @@ class DBHelper(val context: Context) onCreate(database, connectionSource) } - def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, - newVersion: Int) { - if (oldVersion != newVersion) { - if (oldVersion < 7) { - recreate(database, connectionSource) - return - } + def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, newVersion: Int) { + if (oldVersion < 7) { + recreate(database, connectionSource) + return + } - try { - if (oldVersion < 8) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;") - } - if (oldVersion < 9) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';") - } else if (oldVersion < 19) { - profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;") - } - if (oldVersion < 11) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") - } - if (oldVersion < 12) { - profileDao.executeRawNoArgs("BEGIN TRANSACTION;") - profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") - TableUtils.createTable(connectionSource, classOf[Profile]) - profileDao.executeRawNoArgs( - "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + - " udpdns, ipv6, individual) " + - "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," + - " individual FROM `tmp`;") - profileDao.executeRawNoArgs("DROP TABLE `tmp`;") - profileDao.executeRawNoArgs("COMMIT;") - } else if (oldVersion < 13) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;") - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;") - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;") - } + try { + if (oldVersion < 8) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;") + } + if (oldVersion < 9) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';") + } else if (oldVersion < 19) { + profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;") + } + if (oldVersion < 11) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") + } + if (oldVersion < 12) { + profileDao.executeRawNoArgs("BEGIN TRANSACTION;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") + TableUtils.createTable(connectionSource, classOf[Profile]) + profileDao.executeRawNoArgs( + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + + " udpdns, ipv6, individual) " + + "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," + + " individual FROM `tmp`;") + profileDao.executeRawNoArgs("DROP TABLE `tmp`;") + profileDao.executeRawNoArgs("COMMIT;") + } else if (oldVersion < 13) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;") + } - if (oldVersion < 15) { - if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") - var i = 0 - for (profile <- profileDao.queryForAll.asScala) { - if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual) - profile.userOrder = i - profileDao.update(profile) - i += 1 - } + if (oldVersion < 15) { + if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") + var i = 0 + for (profile <- profileDao.queryForAll.asScala) { + if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual) + profile.userOrder = i + profileDao.update(profile) + i += 1 } + } - if (oldVersion < 16) { - profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") - } + if (oldVersion < 16) { + profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") + } - if (oldVersion < 21) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';") - } + if (oldVersion < 21) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';") + } - if (oldVersion < 17) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;") - } else if (oldVersion < 22) { - // upgrade kcptun to SIP003 plugin - profileDao.executeRawNoArgs("BEGIN TRANSACTION;") - profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") - TableUtils.createTable(connectionSource, classOf[Profile]) - profileDao.executeRawNoArgs( - "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " + - "proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " + - "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + - "route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " + - "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") - profileDao.executeRawNoArgs("DROP TABLE `tmp`;") - profileDao.executeRawNoArgs("COMMIT;") - } + if (oldVersion < 17) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;") + } else if (oldVersion < 22) { + // upgrade kcptun to SIP003 plugin + profileDao.executeRawNoArgs("BEGIN TRANSACTION;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") + TableUtils.createTable(connectionSource, classOf[Profile]) + profileDao.executeRawNoArgs( + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " + + "proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " + + "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + + "route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " + + "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") + profileDao.executeRawNoArgs("DROP TABLE `tmp`;") + profileDao.executeRawNoArgs("COMMIT;") + } - if (oldVersion < 23) { - profileDao.executeRawNoArgs("BEGIN TRANSACTION;") - TableUtils.createTable(connectionSource, classOf[KeyValuePair]) - profileDao.executeRawNoArgs("COMMIT;") - import KeyValuePair._ - val old = PreferenceManager.getDefaultSharedPreferences(app) - kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT, - ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array())) - kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN, - ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array())) - } - } catch { - case ex: Exception => - app.track(ex) - recreate(database, connectionSource) - return + if (oldVersion < 23) { + profileDao.executeRawNoArgs("BEGIN TRANSACTION;") + TableUtils.createTable(connectionSource, classOf[KeyValuePair]) + profileDao.executeRawNoArgs("COMMIT;") + import KeyValuePair._ + val old = PreferenceManager.getDefaultSharedPreferences(app) + kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT, + ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array())) + kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN, + ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array())) } + } catch { + case ex: Exception => + app.track(ex) + recreate(database, connectionSource) } } + + override def onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + val connection = new AndroidDatabaseConnection(db, true) + connectionSource.saveSpecialConnection(connection) + recreate(db, connectionSource) + connectionSource.clearSpecialConnection(connection) + } } From be688205ce51cd023ddda6da8d8ceb8543872b5b Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:51:58 -0800 Subject: [PATCH 76/79] Remove obsolete icons --- shadow-notify.png | Bin 7882 -> 0 bytes shadow.png | Bin 33390 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 shadow-notify.png delete mode 100644 shadow.png diff --git a/shadow-notify.png b/shadow-notify.png deleted file mode 100644 index 0b62090345afbe438116be7b2bbe27c1c01f838f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7882 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 diff --git a/shadow.png b/shadow.png deleted file mode 100644 index c7bcd2e5cc6abf749fdc5d94248340398d00c0be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33390 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 Ofd2kNI)z$xk^c+N+$zcd From a6728e602543d3b88ccaa5142d05bd026f09435a Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 21:55:29 -0800 Subject: [PATCH 77/79] Fix default ports not initialized in UI --- .../scala/com/github/shadowsocks/GlobalConfigFragment.scala | 3 +++ .../scala/com/github/shadowsocks/ShadowsocksApplication.scala | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala index cf9720382a..137e18d13f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala @@ -33,6 +33,9 @@ class GlobalConfigFragment extends PreferenceFragmentCompatDividers { override def onCreatePreferencesFix(bundle: Bundle, key: String) { getPreferenceManager.setPreferenceDataStore(app.dataStore) app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug + app.dataStore.putString(Key.portProxy, app.dataStore.portProxy.toString) + app.dataStore.putString(Key.portLocalDns, app.dataStore.portLocalDns.toString) + app.dataStore.putString(Key.portTransproxy, app.dataStore.portTransproxy.toString) addPreferencesFromResource(R.xml.pref_global) val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] switch.setOnPreferenceChangeListener((_, value) => { diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index c134d13844..c27af806c9 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -163,7 +163,6 @@ class ShadowsocksApplication extends Application { TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets() - (dataStore.portProxy, dataStore.portLocalDns, dataStore.portTransproxy) // initialize ports for UI if (Build.VERSION.SDK_INT >= 26) { val nm = getSystemService(classOf[NotificationManager]) From d3903cbc9deec61885953112cfb6e88b00d8abec Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 22:07:12 -0800 Subject: [PATCH 78/79] Fix another crash in Android 4.4 --- .../scala/com/github/shadowsocks/ShadowsocksApplication.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index c27af806c9..a694de81d6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -103,7 +103,8 @@ class ShadowsocksApplication extends Application { case "SG" => SIMPLIFIED_CHINESE case "HK" | "MO" => TRADITIONAL_CHINESE case _ => - Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN...".format(locale.toLanguageTag)) + Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN..." + .formatLocal(Locale.ENGLISH, if (Build.VERSION.SDK_INT >= 21) locale.toLanguageTag else locale)) SIMPLIFIED_CHINESE } } From 2f682ef3ec441ed7f721a7834f03096f1b16d047 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 30 Nov 2017 22:07:32 -0800 Subject: [PATCH 79/79] Fix service reattaching on entering settings --- .../com/github/shadowsocks/GlobalConfigFragment.scala | 5 +---- .../preference/OrmLitePreferenceDataStore.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala index 137e18d13f..1811b72768 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala @@ -32,10 +32,7 @@ import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers class GlobalConfigFragment extends PreferenceFragmentCompatDividers { override def onCreatePreferencesFix(bundle: Bundle, key: String) { getPreferenceManager.setPreferenceDataStore(app.dataStore) - app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug - app.dataStore.putString(Key.portProxy, app.dataStore.portProxy.toString) - app.dataStore.putString(Key.portLocalDns, app.dataStore.portLocalDns.toString) - app.dataStore.putString(Key.portTransproxy, app.dataStore.portTransproxy.toString) + app.dataStore.initGlobal() addPreferencesFromResource(R.xml.pref_global) val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] switch.setOnPreferenceChangeListener((_, value) => { diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala index 034bbfb870..cc3e2387be 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/OrmLitePreferenceDataStore.scala @@ -125,4 +125,12 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat def plugin_=(value: String): Unit = putString(Key.plugin, value) def dirty: Boolean = getBoolean(Key.dirty) def dirty_=(value: Boolean): Unit = putBoolean(Key.dirty, value) + + def initGlobal() { + // temporary workaround for support lib bug + if (getString(Key.serviceMode) == null) putString(Key.serviceMode, serviceMode) + if (getString(Key.portProxy) == null) putString(Key.portProxy, portProxy.toString) + if (getString(Key.portLocalDns) == null) putString(Key.portLocalDns, portLocalDns.toString) + if (getString(Key.portTransproxy) == null) putString(Key.portTransproxy, portTransproxy.toString) + } }