Use one notification for two services
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Application
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.StringRes
|
||||
@@ -23,26 +18,12 @@ class App : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
app = this
|
||||
updateNotificationChannels()
|
||||
ServiceNotification.updateNotificationChannels()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateNotificationChannels()
|
||||
}
|
||||
|
||||
private fun updateNotificationChannels() {
|
||||
if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) {
|
||||
val nm = getSystemService(NotificationManager::class.java)
|
||||
val tethering = NotificationChannel(TetheringService.CHANNEL,
|
||||
getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW)
|
||||
tethering.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
nm.createNotificationChannels(listOf(
|
||||
NotificationChannel(RepeaterService.CHANNEL, getText(R.string.notification_channel_repeater),
|
||||
NotificationManager.IMPORTANCE_LOW), tethering
|
||||
))
|
||||
nm.deleteNotificationChannel("hotspot") // remove old service channel
|
||||
}
|
||||
ServiceNotification.updateNotificationChannels()
|
||||
}
|
||||
|
||||
val handler = Handler()
|
||||
|
||||
@@ -79,7 +79,7 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
|
||||
val ip = neighbour?.ip
|
||||
|
||||
val icon get() = TetherType.ofInterface(iface, p2pInterface).icon
|
||||
val title get() = listOf(ip, mac).filter { !it.isNullOrEmpty() }.joinToString(", ")
|
||||
val title get() = listOf(ip, mac).filter { !it.isNullOrEmpty() }.joinToString()
|
||||
val description get() = when (neighbour?.state) {
|
||||
IpNeighbour.State.INCOMPLETE, null -> "Connecting to $iface"
|
||||
IpNeighbour.State.VALID -> "Connected to $iface"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -13,8 +12,6 @@ import android.os.Binder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.content.LocalBroadcastManager
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
@@ -26,8 +23,6 @@ import java.util.regex.Pattern
|
||||
|
||||
class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Callback {
|
||||
companion object {
|
||||
const val CHANNEL = "repeater"
|
||||
const val CHANNEL_ID = 1
|
||||
const val ACTION_STATUS_CHANGED = "be.mygod.vpnhotspot.RepeaterService.STATUS_CHANGED"
|
||||
const val KEY_NET_ID = "netId"
|
||||
private const val TAG = "RepeaterService"
|
||||
@@ -304,19 +299,8 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNotification(group: WifiP2pGroup? = null) {
|
||||
val builder = NotificationCompat.Builder(this, CHANNEL)
|
||||
.setWhen(0)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
|
||||
.setContentTitle(group?.networkName ?: ssid ?: getString(R.string.repeater_connecting))
|
||||
.setSmallIcon(R.drawable.ic_device_wifi_tethering)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0,
|
||||
Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
val size = group?.clientList?.size ?: 0
|
||||
if (size != 0) builder.setContentText(resources.getQuantityString(R.plurals.notification_connected_devices,
|
||||
size, size, group!!.`interface`))
|
||||
startForeground(CHANNEL_ID, builder.build())
|
||||
}
|
||||
private fun showNotification(group: WifiP2pGroup? = null) = ServiceNotification.startForeground(this,
|
||||
if (group == null) emptyMap() else mapOf(Pair(group.`interface`, group.clientList?.size ?: 0)))
|
||||
|
||||
private fun removeGroup() {
|
||||
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
||||
@@ -345,7 +329,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
|
||||
Toast.makeText(this, getText(R.string.noisy_su_failure), Toast.LENGTH_SHORT).show()
|
||||
routing = null
|
||||
status = Status.IDLE
|
||||
stopForeground(true)
|
||||
ServiceNotification.stopForeground(this)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
|
||||
object ServiceNotification {
|
||||
private const val CHANNEL = "tethering"
|
||||
private const val CHANNEL_ID = 1
|
||||
|
||||
private val deviceCountsMap = HashMap<Service, Map<String, Int>>()
|
||||
private val manager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
private fun buildNotification(context: Context): Notification {
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL)
|
||||
.setWhen(0)
|
||||
.setColor(ContextCompat.getColor(context, R.color.colorPrimary))
|
||||
.setContentTitle("VPN tethering active")
|
||||
.setSmallIcon(R.drawable.ic_device_wifi_tethering)
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0,
|
||||
Intent(context, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
val deviceCounts = deviceCountsMap.values.flatMap { it.entries }.sortedBy { it.key }
|
||||
return when (deviceCounts.size) {
|
||||
0 -> builder.build()
|
||||
1 -> {
|
||||
val (dev, size) = deviceCounts.single()
|
||||
builder.setContentText(context.resources.getQuantityString(R.plurals.notification_connected_devices,
|
||||
size, size, dev))
|
||||
.build()
|
||||
}
|
||||
else -> {
|
||||
val deviceCount = deviceCounts.sumBy { it.value }
|
||||
NotificationCompat.BigTextStyle(builder
|
||||
.setContentText(context.resources.getQuantityString(R.plurals.notification_connected_devices,
|
||||
deviceCount, deviceCount,
|
||||
context.resources.getQuantityString(R.plurals.notification_interfaces,
|
||||
deviceCounts.size, deviceCounts.size))))
|
||||
.bigText(deviceCounts.joinToString("\n") { (dev, size) ->
|
||||
context.resources.getQuantityString(R.plurals.notification_connected_devices,
|
||||
size, size, dev)
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startForeground(service: Service, deviceCounts: Map<String, Int>) {
|
||||
deviceCountsMap[service] = deviceCounts
|
||||
service.startForeground(CHANNEL_ID, buildNotification(service))
|
||||
}
|
||||
fun stopForeground(service: Service) {
|
||||
deviceCountsMap.remove(service)
|
||||
if (deviceCountsMap.isEmpty()) service.stopForeground(true) else {
|
||||
service.stopForeground(false)
|
||||
manager.notify(CHANNEL_ID, buildNotification(service))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNotificationChannels() {
|
||||
if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) {
|
||||
val tethering = NotificationChannel(CHANNEL,
|
||||
app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW)
|
||||
tethering.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
manager.createNotificationChannel(tethering)
|
||||
// remove old service channels
|
||||
manager.deleteNotificationChannel("hotspot")
|
||||
manager.deleteNotificationChannel("repeater")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
package be.mygod.vpnhotspot
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.content.LocalBroadcastManager
|
||||
import android.widget.Toast
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
@@ -14,8 +10,6 @@ import be.mygod.vpnhotspot.net.*
|
||||
|
||||
class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Callback {
|
||||
companion object {
|
||||
const val CHANNEL = "tethering"
|
||||
const val CHANNEL_ID = 2
|
||||
const val EXTRA_ADD_INTERFACE = "interface.add"
|
||||
const val EXTRA_REMOVE_INTERFACE = "interface.remove"
|
||||
}
|
||||
@@ -46,7 +40,7 @@ class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Call
|
||||
private fun updateRoutings() {
|
||||
if (routings.isEmpty()) {
|
||||
unregisterReceiver()
|
||||
stopForeground(true)
|
||||
ServiceNotification.stopForeground(this)
|
||||
stopSelf()
|
||||
} else {
|
||||
val upstream = upstream
|
||||
@@ -106,23 +100,8 @@ class TetheringService : Service(), VpnMonitor.Callback, IpNeighbourMonitor.Call
|
||||
this.neighbours = neighbours.values.toList()
|
||||
}
|
||||
override fun postIpNeighbourAvailable() {
|
||||
val builder = NotificationCompat.Builder(this, CHANNEL)
|
||||
.setWhen(0)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
|
||||
.setContentTitle("VPN tethering active")
|
||||
.setSmallIcon(R.drawable.ic_device_wifi_tethering)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0,
|
||||
Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
val content = neighbours.groupBy { it.dev }
|
||||
.filter { (dev, _) -> routings.contains(dev) }
|
||||
.mapValues { (_, neighbours) -> neighbours.size }
|
||||
.toList()
|
||||
.joinToString(", ") { (dev, size) ->
|
||||
resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev)
|
||||
}
|
||||
if (content.isNotEmpty()) builder.setContentText(content)
|
||||
startForeground(CHANNEL_ID, builder.build())
|
||||
val sizeLookup = neighbours.groupBy { it.dev }.mapValues { (_, neighbours) -> neighbours.size }
|
||||
ServiceNotification.startForeground(this, routings.keys.associate { Pair(it, sizeLookup[it] ?: 0) })
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@@ -51,12 +51,15 @@
|
||||
<string name="settings_misc_donate">Donate</string>
|
||||
<string name="settings_misc_donate_summary">I love money</string>
|
||||
|
||||
<string name="notification_channel_repeater">Repeater Service</string>
|
||||
<string name="notification_channel_tethering">VPN Tethering Service</string>
|
||||
<plurals name="notification_connected_devices">
|
||||
<item quantity="one">%d device connected to %s</item>
|
||||
<item quantity="other">%d devices connected to %s</item>
|
||||
</plurals>
|
||||
<plurals name="notification_interfaces">
|
||||
<item quantity="one">%d interface</item>
|
||||
<item quantity="other">%d interfaces</item>
|
||||
</plurals>
|
||||
|
||||
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
||||
<string name="noisy_su_failure">Something went wrong, please check logcat.</string>
|
||||
|
||||
Reference in New Issue
Block a user