diff --git a/.travis.yml b/.travis.yml
index a9a15ed4..469233a1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ jdk:
env:
global:
- - NDK_VERSION=r14
+ - NDK_VERSION=r15-beta2
- NDK_CCACHE=ccache
- GOROOT_BOOTSTRAP=$GOROOT
- ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
diff --git a/build.sbt b/build.sbt
index ff88b127..5f6450c5 100644
--- a/build.sbt
+++ b/build.sbt
@@ -18,10 +18,12 @@ lazy val commonSettings = Seq(
shrinkResources := true,
typedResources := false,
- resConfigs := Seq("ja", "ko", "ru", "zh-rCN", "zh-rTW")
+ resConfigs := Seq("ja", "ko", "ru", "zh-rCN", "zh-rTW"),
+
+ resolvers += "google" at "https://maven.google.com"
)
-val supportLibsVersion = "26.0.0-alpha1"
+val supportLibsVersion = "26.0.0-beta1"
lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings)
.aggregate(plugin, mobile)
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index 594b7fc7..0d937680 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -56,7 +56,8 @@
Forward all DNS requests to remote
- Service
+ VPN Service
+ NAT Service
Shadowsocks started.
Invalid server name
Failed to connect the remote server
diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
index b9a8ac02..30e4b977 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
@@ -30,6 +30,7 @@ import java.net.Inet6Address
import android.app.Service
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
import android.os.{Handler, IBinder, RemoteCallbackList}
+import android.support.v4.os.BuildCompat
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
@@ -65,6 +66,7 @@ trait BaseService extends Service {
lazy val handler = new Handler(getMainLooper)
lazy val restartHanlder = new Handler(getMainLooper)
+ private var notification: ShadowsocksNotification = _
private val closeReceiver: BroadcastReceiver = (context: Context, _: Intent) => {
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
stopRunner(stopService = true)
@@ -160,10 +162,12 @@ trait BaseService extends Service {
pluginPath = PluginManager.init(plugin)
}
+ def createNotification(): ShadowsocksNotification
def startRunner(profile: Profile) {
this.profile = profile
- startService(new Intent(this, getClass))
+ if (BuildCompat.isAtLeastO) startForegroundService(new Intent(this, getClass))
+ else startService(new Intent(this, getClass))
TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
trafficMonitorThread.start()
@@ -177,6 +181,7 @@ trait BaseService extends Service {
closeReceiverRegistered = true
}
+ notification = createNotification()
app.track(getClass.getSimpleName, "start")
changeState(State.CONNECTING)
@@ -198,6 +203,8 @@ trait BaseService extends Service {
closeReceiverRegistered = false
}
+ if (notification != null) notification.destroy()
+
// Make sure update total traffic when stopping the runner
updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
index 3374eda8..b3aae6e0 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
@@ -256,7 +256,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(R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) {
+ findViewById[View](R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) {
testCount += 1
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
index 4dd063a6..72f8b006 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
@@ -44,6 +44,7 @@ import com.j256.ormlite.logger.LocalLog
import eu.chainfire.libsuperuser.Shell
import scala.collection.mutable.ArrayBuffer
+import scala.collection.JavaConversions._
object ShadowsocksApplication {
var app: ShadowsocksApplication = _
@@ -156,8 +157,10 @@ class ShadowsocksApplication extends Application {
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
- if (BuildCompat.isAtLeastO) getSystemService(classOf[NotificationManager]).createNotificationChannel(
- new NotificationChannel("service", getText(R.string.service), NotificationManager.IMPORTANCE_MIN))
+ if (BuildCompat.isAtLeastO) 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() {
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
index d5855834..a71ea95d 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
@@ -43,7 +43,6 @@ class ShadowsocksNatService extends BaseService {
val CMD_IPTABLES_DNAT_ADD_SOCKS =
"iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123"
- private var notification: ShadowsocksNotification = _
val myUid: Int = android.os.Process.myUid()
var sslocalProcess: GuardedProcess = _
@@ -204,12 +203,11 @@ class ShadowsocksNatService extends BaseService {
AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED)
- notification = new ShadowsocksNotification(this, profile.name, true)
}
- override def stopRunner(stopService: Boolean, msg: String = null) {
+ override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true)
- if (notification != null) notification.destroy()
+ override def stopRunner(stopService: Boolean, msg: String = null) {
// channge the state
changeState(State.STOPPING)
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
index 9fc38fc9..7ca79437 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
@@ -35,7 +35,8 @@ import com.github.shadowsocks.utils.{Action, State, TrafficMonitor, Utils}
/**
* @author Mygod
*/
-class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) {
+class ShadowsocksNotification(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 {
@@ -61,7 +62,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
.setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[MainActivity])
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
.setSmallIcon(R.drawable.ic_stat_shadowsocks)
- .setChannel("service")
+ .setChannel(channel)
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close,
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, new Intent(Action.CLOSE), 0))
private lazy val style = new BigTextStyle(builder)
@@ -99,9 +100,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
show()
} else if (forceShow) show()
- def show(): Unit =
- if (BuildCompat.isAtLeastO) nm.startServiceInForeground(new Intent(service, service.getClass), 1, builder.build())
- else service.startForeground(1, builder.build)
+ def show(): Unit = service.startForeground(1, builder.build)
def destroy() {
if (lockReceiver != null) {
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
index 81ac83ee..5e4d1e34 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -42,7 +42,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
var conn: ParcelFileDescriptor = _
var vpnThread: ShadowsocksVpnThread = _
- private var notification: ShadowsocksNotification = _
var sslocalProcess: GuardedProcess = _
var overtureProcess: GuardedProcess = _
@@ -69,8 +68,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
vpnThread = null
}
- if (notification != null) notification.destroy()
-
// channge the state
changeState(State.STOPPING)
@@ -117,6 +114,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
super.startRunner(profile)
}
+ override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-vpn")
+
override def connect() {
super.connect()
@@ -137,8 +136,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES)
AclSyncJob.schedule(profile.route)
-
- notification = new ShadowsocksNotification(this, profile.name)
}
/** Called when the activity is first created. */