mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 02:12:27 +00:00
WIP: make service foreground
This commit is contained in:
@@ -45,7 +45,7 @@ android {
|
|||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.illusionware.lyricsgrabbr.lyricsgrabbr"
|
applicationId "com.illusionware.lyricsgrabbr.lyricsgrabbr"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 31
|
targetSdkVersion 30
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
@@ -65,4 +65,5 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.illusionware.lyricsgrabbr.lyricsgrabbr">
|
package="com.illusionware.lyricsgrabbr.lyricsgrabbr">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Lyrics Grabbr"
|
android:label="Lyrics Grabbr"
|
||||||
android:name="${applicationName}"
|
android:name=".App"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.StrictMode
|
||||||
|
|
||||||
|
class App : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
StrictMode.setThreadPolicy(
|
||||||
|
StrictMode.ThreadPolicy.Builder()
|
||||||
|
.detectAll() // or .detectAll() for all detectable problems
|
||||||
|
.penaltyLog()
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
StrictMode.setVmPolicy(
|
||||||
|
StrictMode.VmPolicy.Builder()
|
||||||
|
.detectAll()
|
||||||
|
.penaltyLog()
|
||||||
|
.penaltyDeath()
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
-12
@@ -1,15 +1,20 @@
|
|||||||
package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
private var streamHandler : NotificationStreamHandler? = null
|
// private var streamHandler : NotificationStreamHandler? = null
|
||||||
|
private var listenerIntent : Intent? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"
|
private const val ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"
|
||||||
@@ -17,10 +22,12 @@ class MainActivity: FlutterActivity() {
|
|||||||
private const val METHOD_CHANNEL_NAME = "notifications/method"
|
private const val METHOD_CHANNEL_NAME = "notifications/method"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL_NAME)
|
listenerIntent = Intent(applicationContext, NotificationListener::class.java)
|
||||||
|
|
||||||
|
val methodChannel = MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, METHOD_CHANNEL_NAME)
|
||||||
methodChannel.setMethodCallHandler { call, result ->
|
methodChannel.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"plugin.hasPermission" -> {
|
"plugin.hasPermission" -> {
|
||||||
@@ -29,18 +36,19 @@ class MainActivity: FlutterActivity() {
|
|||||||
"plugin.openPermissionSettings" -> {
|
"plugin.openPermissionSettings" -> {
|
||||||
result.success(openPermissionSettings())
|
result.success(openPermissionSettings())
|
||||||
}
|
}
|
||||||
|
"plugin.startService" -> {
|
||||||
|
result.success(startService())
|
||||||
|
}
|
||||||
|
"plugin.stopService" -> {
|
||||||
|
result.success(stopService())
|
||||||
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_NAME)
|
// val eventChannel = EventChannel(flutterEngine!!.dartExecutor.binaryMessenger, EVENT_CHANNEL_NAME)
|
||||||
streamHandler = NotificationStreamHandler(context)
|
// streamHandler = NotificationStreamHandler(context)
|
||||||
eventChannel.setStreamHandler(streamHandler)
|
// eventChannel.setStreamHandler(streamHandler)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
streamHandler?.cleanUp()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isPermissionGranted(): Boolean {
|
private fun isPermissionGranted(): Boolean {
|
||||||
@@ -59,4 +67,21 @@ class MainActivity: FlutterActivity() {
|
|||||||
context.startActivity(Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
context.startActivity(Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startService(): Boolean {
|
||||||
|
listenerIntent?.action = NotificationListener.START_FOREGROUND_SERVICE_ACTION
|
||||||
|
ContextCompat.startForegroundService(applicationContext, listenerIntent!!)
|
||||||
|
Log.d("startService", "starting")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopService(): Boolean {
|
||||||
|
listenerIntent?.action = NotificationListener.STOP_FOREGROUND_SERVICE_ACTION
|
||||||
|
ContextCompat.startForegroundService(applicationContext, listenerIntent!!)
|
||||||
|
Log.d("stopService", "stopping")
|
||||||
|
//applicationContext.stopService(listenerIntent!!)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+91
-37
@@ -1,26 +1,58 @@
|
|||||||
package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.media.MediaMetadata
|
import android.media.MediaMetadata
|
||||||
import android.media.session.MediaController
|
import android.media.session.MediaController
|
||||||
import android.media.session.MediaSession
|
import android.media.session.MediaSession
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
import android.service.notification.NotificationListenerService
|
import android.service.notification.NotificationListenerService
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import org.json.JSONException
|
import android.util.Log
|
||||||
import org.json.JSONObject
|
import androidx.core.app.NotificationCompat
|
||||||
import android.os.Bundle
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification listening service. Intercepts notifications if permission is given to do so.
|
* Notification listening service. Intercepts notifications if permission is given to do so.
|
||||||
*/
|
*/
|
||||||
class NotificationListener : NotificationListenerService() {
|
class NotificationListener : NotificationListenerService() {
|
||||||
|
private var notificationManager : NotificationManagerCompat? = null
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Log.d("onCreate Service", "creating service")
|
||||||
|
|
||||||
|
initNotificationChannels(this)
|
||||||
|
notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d("onDestroy Service", "destroying service")
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
if (intent != null) {
|
||||||
|
when (intent.action) {
|
||||||
|
START_FOREGROUND_SERVICE_ACTION -> {
|
||||||
|
Log.d("START", "starting foreground service")
|
||||||
|
startForegroundService()
|
||||||
getCurrentNotifications(applicationContext)
|
getCurrentNotifications(applicationContext)
|
||||||
|
}
|
||||||
|
STOP_FOREGROUND_SERVICE_ACTION -> {
|
||||||
|
Log.d("STOP", "stopping foreground service")
|
||||||
|
stopForegroundService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,14 +61,46 @@ class NotificationListener : NotificationListenerService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||||
|
if (sbn.packageName == SPOTIFY_PACKAGE) {
|
||||||
sendNotificationBroadcast(sbn, applicationContext)
|
sendNotificationBroadcast(sbn, applicationContext)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startForegroundService() {
|
||||||
|
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setContentTitle("TITLE")
|
||||||
|
.setContentText("TEXT")
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notification.flags = Notification.FLAG_FOREGROUND_SERVICE
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopForegroundService() {
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initNotificationChannels(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT < 26) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID,
|
||||||
|
"Lyrics",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getCurrentNotifications(context: Context) {
|
private fun getCurrentNotifications(context: Context) {
|
||||||
val sbns = activeNotifications
|
val sbns = activeNotifications
|
||||||
for (sbn in sbns) {
|
for (sbn in sbns) {
|
||||||
// TODO: remove this check and figure out a way to send a fucking list instead
|
if (sbn.packageName == SPOTIFY_PACKAGE) {
|
||||||
if (sbn.packageName == "com.spotify.music") {
|
|
||||||
sendNotificationBroadcast(sbn, context)
|
sendNotificationBroadcast(sbn, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,19 +112,7 @@ class NotificationListener : NotificationListenerService() {
|
|||||||
val mediaCtrl = mediaSessionToken?.let { MediaController(context, it) }
|
val mediaCtrl = mediaSessionToken?.let { MediaController(context, it) }
|
||||||
val duration = mediaCtrl?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000)
|
val duration = mediaCtrl?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000)
|
||||||
val playbackState = mediaCtrl?.playbackState?.state
|
val playbackState = mediaCtrl?.playbackState?.state
|
||||||
|
|
||||||
var albumArtPath = ""
|
|
||||||
|
|
||||||
if (sbn.packageName == "com.spotify.music") {
|
|
||||||
val albumArt = mediaCtrl?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
|
val albumArt = mediaCtrl?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
|
||||||
val tempArtFile = File(cacheDir, "albumart_temp.png")
|
|
||||||
tempArtFile.createNewFile()
|
|
||||||
val fileOutputStream = FileOutputStream(tempArtFile)
|
|
||||||
albumArt?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
|
|
||||||
fileOutputStream.flush()
|
|
||||||
fileOutputStream.close()
|
|
||||||
albumArtPath = tempArtFile.absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve package name to set as title.
|
// Retrieve package name to set as title.
|
||||||
val packageName = sbn.packageName
|
val packageName = sbn.packageName
|
||||||
@@ -73,10 +125,22 @@ class NotificationListener : NotificationListenerService() {
|
|||||||
intent.putExtra(NOTIFICATION_PACKAGE_NAME, packageName)
|
intent.putExtra(NOTIFICATION_PACKAGE_NAME, packageName)
|
||||||
intent.putExtra(NOTIFICATION_PACKAGE_MESSAGE, packageMessage)
|
intent.putExtra(NOTIFICATION_PACKAGE_MESSAGE, packageMessage)
|
||||||
intent.putExtra(NOTIFICATION_PACKAGE_TEXT, packageText)
|
intent.putExtra(NOTIFICATION_PACKAGE_TEXT, packageText)
|
||||||
intent.putExtra(NOTIFICATION_PACKAGE_ART, albumArtPath)
|
|
||||||
intent.putExtra(NOTIFICATION_PACKAGE_DURATION, duration)
|
intent.putExtra(NOTIFICATION_PACKAGE_DURATION, duration)
|
||||||
intent.putExtra(NOTIFICATION_PACKAGE_PLAYBACK_STATE, playbackState)
|
intent.putExtra(NOTIFICATION_PACKAGE_PLAYBACK_STATE, playbackState)
|
||||||
sendBroadcast(intent)
|
|
||||||
|
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setContentTitle("Playing [${packageText}] By [${packageMessage}]")
|
||||||
|
.setContentText("Tap to get lyrics")
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setLargeIcon(albumArt)
|
||||||
|
// .setContentIntent(pendingIntent) // launch intent when clicking on notification
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notification.flags = Notification.FLAG_FOREGROUND_SERVICE
|
||||||
|
|
||||||
|
notificationManager?.notify(NOTIFICATION_ID, notification)
|
||||||
|
|
||||||
|
//sendBroadcast(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -84,22 +148,12 @@ class NotificationListener : NotificationListenerService() {
|
|||||||
const val NOTIFICATION_PACKAGE_NAME = "package_name"
|
const val NOTIFICATION_PACKAGE_NAME = "package_name"
|
||||||
const val NOTIFICATION_PACKAGE_MESSAGE = "package_message"
|
const val NOTIFICATION_PACKAGE_MESSAGE = "package_message"
|
||||||
const val NOTIFICATION_PACKAGE_TEXT = "package_text"
|
const val NOTIFICATION_PACKAGE_TEXT = "package_text"
|
||||||
const val NOTIFICATION_PACKAGE_EXTRA = "package_extra"
|
|
||||||
const val NOTIFICATION_PACKAGE_ART = "package_art"
|
|
||||||
const val NOTIFICATION_PACKAGE_PLAYBACK_STATE = "package_playback_state"
|
const val NOTIFICATION_PACKAGE_PLAYBACK_STATE = "package_playback_state"
|
||||||
const val NOTIFICATION_PACKAGE_DURATION = "package_duration"
|
const val NOTIFICATION_PACKAGE_DURATION = "package_duration"
|
||||||
}
|
const val NOTIFICATION_ID = 12251999
|
||||||
|
const val NOTIFICATION_CHANNEL_ID = "Lyrics"
|
||||||
private fun convertBundleToJsonString(extra: Bundle): String {
|
const val SPOTIFY_PACKAGE = "com.spotify.music"
|
||||||
val json = JSONObject()
|
const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE"
|
||||||
val keys = extra.keySet()
|
const val STOP_FOREGROUND_SERVICE_ACTION = "STOP_SERVICE"
|
||||||
for (key in keys) {
|
|
||||||
try {
|
|
||||||
json.put(key, JSONObject.wrap(extra.get(key)))
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
//Handle exception here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.toString()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -3,34 +3,38 @@ package com.illusionware.lyricsgrabbr.lyricsgrabbr
|
|||||||
import io.flutter.plugin.common.EventChannel.StreamHandler
|
import io.flutter.plugin.common.EventChannel.StreamHandler
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
import android.content.*
|
import android.content.*
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
class NotificationStreamHandler(private val context: Context) : StreamHandler {
|
class NotificationStreamHandler(private val context: Context) : StreamHandler {
|
||||||
private var eventSink: EventSink? = null
|
private var eventSink: EventSink? = null
|
||||||
private var receiver: NotificationReceiver? = null
|
private var receiver: NotificationReceiver? = null
|
||||||
private val listenerIntent = Intent(context, NotificationListener::class.java)
|
|
||||||
|
|
||||||
// Called whenever the event channel is subscribed to in Flutter
|
// Called whenever the event channel is subscribed to in Flutter
|
||||||
override fun onListen(o: Any?, eventSink: EventSink?) {
|
override fun onListen(o: Any?, eventSink: EventSink?) {
|
||||||
this.eventSink = eventSink
|
this.eventSink = eventSink
|
||||||
|
|
||||||
receiver = NotificationReceiver()
|
val listenerIntent = Intent(context, NotificationListener::class.java)
|
||||||
val intentFilter = IntentFilter()
|
listenerIntent.action = NotificationListener.START_FOREGROUND_SERVICE_ACTION
|
||||||
intentFilter.addAction(NotificationListener.NOTIFICATION_INTENT)
|
|
||||||
context.registerReceiver(receiver, intentFilter)
|
|
||||||
|
|
||||||
context.startService(listenerIntent)
|
//receiver = NotificationReceiver()
|
||||||
|
//val intentFilter = IntentFilter()
|
||||||
|
//intentFilter.addAction(NotificationListener.NOTIFICATION_INTENT)
|
||||||
|
//context.registerReceiver(receiver, intentFilter)
|
||||||
|
|
||||||
|
ContextCompat.startForegroundService(context, listenerIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called whenever the event channel subscription is cancelled in Flutter
|
// Called whenever the event channel subscription is cancelled in Flutter
|
||||||
override fun onCancel(o: Any?) {
|
override fun onCancel(o: Any?) {
|
||||||
eventSink = null
|
eventSink = null
|
||||||
|
Log.d("onCancel", "subscription cancelled, stopping service")
|
||||||
|
|
||||||
context.stopService(listenerIntent)
|
val listenerIntent = Intent(context, NotificationListener::class.java)
|
||||||
context.unregisterReceiver(receiver)
|
listenerIntent.action = NotificationListener.STOP_FOREGROUND_SERVICE_ACTION
|
||||||
}
|
|
||||||
|
|
||||||
fun cleanUp() {
|
ContextCompat.startForegroundService(context, listenerIntent)
|
||||||
context.unregisterReceiver(receiver)
|
//context.unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal inner class NotificationReceiver : BroadcastReceiver() {
|
internal inner class NotificationReceiver : BroadcastReceiver() {
|
||||||
@@ -38,14 +42,12 @@ class NotificationStreamHandler(private val context: Context) : StreamHandler {
|
|||||||
val packageName = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_NAME)
|
val packageName = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_NAME)
|
||||||
val packageMessage = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_MESSAGE)
|
val packageMessage = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_MESSAGE)
|
||||||
val packageText = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_TEXT)
|
val packageText = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_TEXT)
|
||||||
val packageArt = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_ART)
|
|
||||||
val packagePlaybackState = intent.getIntExtra(NotificationListener.NOTIFICATION_PACKAGE_PLAYBACK_STATE, -1)
|
val packagePlaybackState = intent.getIntExtra(NotificationListener.NOTIFICATION_PACKAGE_PLAYBACK_STATE, -1)
|
||||||
val packageDuration = intent.getLongExtra(NotificationListener.NOTIFICATION_PACKAGE_DURATION, -1)
|
val packageDuration = intent.getLongExtra(NotificationListener.NOTIFICATION_PACKAGE_DURATION, -1)
|
||||||
val map = HashMap<String, Any>()
|
val map = HashMap<String, Any>()
|
||||||
map["packageName"] = packageName!!
|
map["packageName"] = packageName!!
|
||||||
map["packageMessage"] = packageMessage!!
|
map["packageMessage"] = packageMessage!!
|
||||||
map["packageText"] = packageText!!
|
map["packageText"] = packageText!!
|
||||||
map["packageArt"] = packageArt!!
|
|
||||||
map["packagePlaybackState"] = packagePlaybackState
|
map["packagePlaybackState"] = packagePlaybackState
|
||||||
map["packageDuration"] = packageDuration
|
map["packageDuration"] = packageDuration
|
||||||
eventSink?.success(map)
|
eventSink?.success(map)
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536M
|
## For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
#
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||||
|
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
#
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
#Sun Feb 27 21:16:18 EET 2022
|
||||||
|
org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx1024M"
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|||||||
+5
-54
@@ -2,34 +2,11 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'notification_listener.dart';
|
import 'notification_listener.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
||||||
FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
const spotifyPackage = "com.spotify.music";
|
|
||||||
const notificationId = 0;
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
|
||||||
AndroidInitializationSettings('@drawable/ic_notification');
|
|
||||||
const InitializationSettings initializationSettings =
|
|
||||||
InitializationSettings(android: initializationSettingsAndroid);
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
|
||||||
onSelectNotification: onNotificationClick);
|
|
||||||
|
|
||||||
runApp(const LyricsGrabbr());
|
runApp(const LyricsGrabbr());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNotificationClick(String? payload) async {
|
|
||||||
if (payload != null) {
|
|
||||||
print('notification payload: $payload');
|
|
||||||
}
|
|
||||||
print('notification pressed');
|
|
||||||
}
|
|
||||||
|
|
||||||
class LyricsGrabbr extends StatelessWidget {
|
class LyricsGrabbr extends StatelessWidget {
|
||||||
const LyricsGrabbr({Key? key}) : super(key: key);
|
const LyricsGrabbr({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -135,56 +112,30 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onData(NotificationEvent event) {
|
void onData(NotificationEvent event) {
|
||||||
if (event.packageName == spotifyPackage) {
|
|
||||||
// TODO: if the app is in the background, only update our notif
|
// TODO: if the app is in the background, only update our notif
|
||||||
// if the app is in the foreground, update the notif, and
|
// if the app is in the foreground, update the notif, and
|
||||||
// fetch the new lyrics and display them.
|
// fetch the new lyrics and display them.
|
||||||
sendNotification(event);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_notifs.add(event);
|
_notifs.add(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> sendNotification(NotificationEvent notification) async {
|
|
||||||
final AndroidNotificationDetails androidNotification =
|
|
||||||
AndroidNotificationDetails(
|
|
||||||
"Lyrics",
|
|
||||||
"Lyrics",
|
|
||||||
importance: Importance.low,
|
|
||||||
priority: Priority.min,
|
|
||||||
showWhen: false,
|
|
||||||
ongoing: true,
|
|
||||||
playSound: false,
|
|
||||||
largeIcon: FilePathAndroidBitmap(notification.art),
|
|
||||||
);
|
|
||||||
|
|
||||||
final NotificationDetails notificationDetails =
|
|
||||||
NotificationDetails(android: androidNotification);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
notificationId,
|
|
||||||
'Playing [${notification.songTitle}] By [${notification.artist}]',
|
|
||||||
'Tap to get lyrics',
|
|
||||||
notificationDetails,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void startListening() {
|
void startListening() {
|
||||||
// TODO: make the FAB start and stop a local notification service
|
// TODO: make the FAB start and stop a local notification service
|
||||||
// that keeps track of which song is playing
|
// that keeps track of which song is playing
|
||||||
// and if pressed will fetch the lyrics and display them.
|
// and if pressed will fetch the lyrics and display them.
|
||||||
|
|
||||||
_notifications = AndroidNotificationListener();
|
//_notifications = AndroidNotificationListener();
|
||||||
|
|
||||||
_subscription = _notifications.notificationStream.listen(onData);
|
//_subscription = _notifications.notificationStream.listen(onData);
|
||||||
|
AndroidNotificationListener.startService();
|
||||||
setState(() => running = true);
|
setState(() => running = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopListening() {
|
void stopListening() {
|
||||||
setState(() => running = false);
|
setState(() => running = false);
|
||||||
_subscription.cancel();
|
AndroidNotificationListener.stopService();
|
||||||
flutterLocalNotificationsPlugin.cancel(notificationId);
|
//_subscription.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class NotificationEvent {
|
|||||||
final String artist;
|
final String artist;
|
||||||
final String packageName;
|
final String packageName;
|
||||||
final String songTitle;
|
final String songTitle;
|
||||||
final String art;
|
|
||||||
final int playbackState;
|
final int playbackState;
|
||||||
final int duration;
|
final int duration;
|
||||||
|
|
||||||
@@ -34,7 +33,6 @@ class NotificationEvent {
|
|||||||
required this.artist,
|
required this.artist,
|
||||||
required this.timeStamp,
|
required this.timeStamp,
|
||||||
required this.songTitle,
|
required this.songTitle,
|
||||||
required this.art,
|
|
||||||
required this.playbackState,
|
required this.playbackState,
|
||||||
required this.duration
|
required this.duration
|
||||||
});
|
});
|
||||||
@@ -44,7 +42,6 @@ class NotificationEvent {
|
|||||||
String name = map['packageName'];
|
String name = map['packageName'];
|
||||||
String message = map['packageMessage'];
|
String message = map['packageMessage'];
|
||||||
String text = map['packageText'];
|
String text = map['packageText'];
|
||||||
String icon = map['packageArt'];
|
|
||||||
int playbackState = map['packagePlaybackState'];
|
int playbackState = map['packagePlaybackState'];
|
||||||
int duration = map['packageDuration'];
|
int duration = map['packageDuration'];
|
||||||
|
|
||||||
@@ -53,7 +50,6 @@ class NotificationEvent {
|
|||||||
artist: message,
|
artist: message,
|
||||||
timeStamp: time,
|
timeStamp: time,
|
||||||
songTitle: text,
|
songTitle: text,
|
||||||
art: icon,
|
|
||||||
playbackState: playbackState,
|
playbackState: playbackState,
|
||||||
duration: duration
|
duration: duration
|
||||||
);
|
);
|
||||||
@@ -89,4 +85,12 @@ class AndroidNotificationListener {
|
|||||||
static Future<bool?> openPermissionSettings() async {
|
static Future<bool?> openPermissionSettings() async {
|
||||||
return await _methodChannel.invokeMethod('plugin.openPermissionSettings');
|
return await _methodChannel.invokeMethod('plugin.openPermissionSettings');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool?> startService() async {
|
||||||
|
return await _methodChannel.invokeMethod('plugin.startService');
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool?> stopService() async {
|
||||||
|
return await _methodChannel.invokeMethod('plugin.stopService');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user