Commit f04106a4 authored by Mygod's avatar Mygod

Hide VPN notification on Android 9+

Refine #1355.
This commit also deprecates hiding non-VPN service notification on lockscreen.
parent 198d84c3
...@@ -135,7 +135,8 @@ object Core { ...@@ -135,7 +135,8 @@ object Core {
val nm = app.getSystemService<NotificationManager>()!! val nm = app.getSystemService<NotificationManager>()!!
nm.createNotificationChannels(listOf( nm.createNotificationChannels(listOf(
NotificationChannel("service-vpn", app.getText(R.string.service_vpn), NotificationChannel("service-vpn", app.getText(R.string.service_vpn),
NotificationManager.IMPORTANCE_LOW), if (Build.VERSION.SDK_INT >= 28) NotificationManager.IMPORTANCE_MIN
else NotificationManager.IMPORTANCE_LOW), // #1355
NotificationChannel("service-proxy", app.getText(R.string.service_proxy), NotificationChannel("service-proxy", app.getText(R.string.service_proxy),
NotificationManager.IMPORTANCE_LOW), NotificationManager.IMPORTANCE_LOW),
NotificationChannel("service-transproxy", app.getText(R.string.service_transproxy), NotificationChannel("service-transproxy", app.getText(R.string.service_transproxy),
......
...@@ -20,10 +20,9 @@ ...@@ -20,10 +20,9 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
import android.app.KeyguardManager
import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
...@@ -38,18 +37,19 @@ import com.github.shadowsocks.aidl.IShadowsocksServiceCallback ...@@ -38,18 +37,19 @@ import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.aidl.TrafficStats import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver
/** /**
* Android < 8 VPN: always invisible because of VPN notification/icon * User can customize visibility of notification since Android 8.
* Android < 8 other: only invisible in (possibly unsecure) lockscreen * The default visibility:
* Android 8+: always visible due to system limitations *
* (user can choose to hide the notification in secure lockscreen or anywhere) * Android 8.x: always visible due to system limitations
* VPN: always invisible because of VPN notification/icon
* Other: always visible
*
* See also: https://github.com/aosp-mirror/platform_frameworks_base/commit/070d142993403cc2c42eca808ff3fafcee220ac4
*/ */
class ServiceNotification(private val service: BaseService.Interface, profileName: String, class ServiceNotification(private val service: BaseService.Interface, profileName: String,
channel: String, private val visible: Boolean = false) { channel: String, visible: Boolean = false) : BroadcastReceiver() {
private val keyGuard = (service as Context).getSystemService<KeyguardManager>()!!
private val nm by lazy { (service as Context).getSystemService<NotificationManager>()!! }
private val callback: IShadowsocksServiceCallback by lazy { private val callback: IShadowsocksServiceCallback by lazy {
object : IShadowsocksServiceCallback.Stub() { object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore
...@@ -68,7 +68,6 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -68,7 +68,6 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
override fun trafficPersisted(profileId: Long) { } override fun trafficPersisted(profileId: Long) { }
} }
} }
private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) }
private var callbackRegistered = false private var callbackRegistered = false
private val builder = NotificationCompat.Builder(service as Context, channel) private val builder = NotificationCompat.Builder(service as Context, channel)
...@@ -79,7 +78,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -79,7 +78,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
.setContentIntent(Core.configureIntent(service)) .setContentIntent(Core.configureIntent(service))
.setSmallIcon(R.drawable.ic_service_active) .setSmallIcon(R.drawable.ic_service_active)
.setCategory(NotificationCompat.CATEGORY_SERVICE) .setCategory(NotificationCompat.CATEGORY_SERVICE)
private var isVisible = true .setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN)
init { init {
service as Context service as Context
...@@ -90,52 +89,34 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -90,52 +89,34 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
setShowsUserInterface(false) setShowsUserInterface(false)
}.build() }.build()
if (Build.VERSION.SDK_INT < 24) builder.addAction(closeAction) else builder.addInvisibleAction(closeAction) if (Build.VERSION.SDK_INT < 24) builder.addAction(closeAction) else builder.addInvisibleAction(closeAction)
update(if (service.getSystemService<PowerManager>()?.isInteractive != false) updateCallback(service.getSystemService<PowerManager>()?.isInteractive != false)
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) service.registerReceiver(this, IntentFilter().apply {
service.registerReceiver(lockReceiver, IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON) addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_SCREEN_OFF)
if (visible && Build.VERSION.SDK_INT < 26) addAction(Intent.ACTION_USER_PRESENT)
}) })
show()
} }
private fun update(action: String?, forceShow: Boolean = false) { override fun onReceive(context: Context, intent: Intent) {
if (forceShow || service.data.state == BaseService.State.Connected) when (action) { if (service.data.state == BaseService.State.Connected) updateCallback(intent.action == Intent.ACTION_SCREEN_ON)
Intent.ACTION_SCREEN_OFF -> {
setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery
} }
Intent.ACTION_SCREEN_ON -> {
setVisible(visible && !keyGuard.isKeyguardLocked, forceShow) private fun updateCallback(screenOn: Boolean) {
if (screenOn) {
service.data.binder.registerCallback(callback) service.data.binder.registerCallback(callback)
service.data.binder.startListeningForBandwidth(callback, 1000) service.data.binder.startListeningForBandwidth(callback, 1000)
callbackRegistered = true callbackRegistered = true
} } else if (callbackRegistered) { // unregister callback to save battery
Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow)
}
}
private fun unregisterCallback() {
if (callbackRegistered) {
service.data.binder.unregisterCallback(callback) service.data.binder.unregisterCallback(callback)
callbackRegistered = false callbackRegistered = false
} }
} }
private fun setVisible(visible: Boolean, forceShow: Boolean = false) {
if (isVisible != visible) {
isVisible = visible
builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN
show()
} else if (forceShow) show()
}
private fun show() = (service as Service).startForeground(1, builder.build()) private fun show() = (service as Service).startForeground(1, builder.build())
fun destroy() { fun destroy() {
(service as Service).unregisterReceiver(lockReceiver) (service as Service).unregisterReceiver(this)
unregisterCallback() updateCallback(false)
service.stopForeground(true) service.stopForeground(true)
nm.cancel(1)
} }
} }
...@@ -118,8 +118,10 @@ class KeyValuePair() { ...@@ -118,8 +118,10 @@ class KeyValuePair() {
fun put(value: Set<String>): KeyValuePair { fun put(value: Set<String>): KeyValuePair {
valueType = TYPE_STRING_SET valueType = TYPE_STRING_SET
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
val intBuffer = ByteBuffer.allocate(4)
for (v in value) { for (v in value) {
stream.write(ByteBuffer.allocate(4).putInt(v.length).array()) intBuffer.reset()
stream.write(intBuffer.putInt(v.length).array())
stream.write(v.toByteArray()) stream.write(v.toByteArray())
} }
this.value = stream.toByteArray() this.value = stream.toByteArray()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment