I'm trying to implement a service that will listen to the incoming notifications. I've googled some and found NotificationListenerService.
The way I want to work with the service is the following:
- user can start/stop the service by interacting with two buttons (Start service, stop service)
- once the service is started it should run in the background until it is explicitly stopped by the user (using the stop service button)
- when the service is started, if the user opens the app I want to let the user interact with the service (as an example, call
cancelAllNotifications()on a button click)
Buttons - user interaction with the service
button_start.setOnClickListener {
logDebug("Button start clicked.")
startService()
}
//BUG service doesn't seem to stop
button_stop.setOnClickListener {
logDebug("Button stop clicked.")
stopService()
}
button_cancel_notif.setOnClickListener {
logDebug("Button cancel all notifications clicked.")
cancelAllNotifications()
}
I've managed to start the service and bind it to the fragment, but when I call stopService(), even though it returns true, the service won't stop.
Here is my code:
AndroidManifest
<service android:name=".services.NLService"
android:label="@string/service_name"
android:exported="false"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
class NLService
class NLService : NotificationListenerService() {
private val binder = NLBinder()
var isBound: Boolean = false
override fun onBind(intent: Intent?): IBinder? {
logDebug("NLService is bind!")
isBound = true
val action = intent?.action
logDebug("onBind action: $action")
return if (action == SERVICE_INTERFACE) {
logDebug("Bound by system")
super.onBind(intent)
} else {
logDebug("Bound by application")
binder
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return Service.START_STICKY
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
logDebug(sbn.toString())
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
super.onNotificationRemoved(sbn)
logDebug(sbn.toString())
}
inner class NLBinder : Binder() {
fun getService(): NLService = this@NLService
}
}
ServiceFragment
open class ServiceFragment : Fragment(), INLServiceActions, INLBinderActions {
private lateinit var notificationListenerIntent: Intent
private var notificationListenerService: NLService? = null
private lateinit var binder: NLService.NLBinder
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
logDebug("onServiceConnected called.")
binder = service as NLService.NLBinder
notificationListenerService = binder.getService()
}
}
override fun onStop() {
super.onStop()
unbindService(connection, notificationListenerService)
}
override fun onResume() {
super.onResume()
notificationListenerIntent = Intent(activity?.applicationContext, NLService::class.java)
activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
if (isServiceRunning) {
bindService(notificationListenerIntent, connection, 0)
} else {
logDebug("Service is not running!")
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_NOTIFICATION_LISTENER -> startService(notificationListenerIntent)
}
}
override fun stopService() {
//BUG service doesn't seem to stop
activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
if (isServiceRunning) {
unbindService(connection, notificationListenerService)
stopService(notificationListenerIntent)
} else {
logDebug("Service is not running!")
}
}
}
override fun startService() {
activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
if (!isServiceRunning) {
requestEnableNLService()
} else {
logDebug("Service is already running!")
}
}
}
override fun cancelAllNotifications() {
notificationListenerService?.cancelAllNotifications()
}
private fun requestEnableNLService() {
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
startActivityForResult(intent, REQUEST_NOTIFICATION_LISTENER)
}
}
Extension functions
@Suppress("DEPRECATION") // Deprecated for third party Services.
fun <T> Activity.isServiceRunning(serviceClass: Class<T>): Boolean {
return (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
.getRunningServices(Integer.MAX_VALUE)
.any { it.service.className == serviceClass.name }
}
fun Fragment.bindService(
intent: Intent,
connection: ServiceConnection,
flags: Int
) {
val isBind = activity?.applicationContext?.bindService(intent, connection, flags)
logDebug("Service bind: $isBind.")
}
fun Fragment.unbindService(
connection: ServiceConnection,
notificationListenerService: NLService?
) {
notificationListenerService?.isBound?.let {
if (it) {
activity?.applicationContext?.unbindService(connection)
notificationListenerService.isBound = false
logDebug("Service unbind.")
}
}
}
fun Fragment.startService(service: Intent) {
activity?.applicationContext?.startService(service)
logDebug("Service started")
}
fun Fragment.stopService(service: Intent) {
val isStopped = activity?.applicationContext?.stopService(service)
logDebug("Service stopped: $isStopped")
}