it is my first time using maps sdk and i am having a lot of trouble to learn how to use it. I am creating an application which will send user's location to backend for each significant displacement, and a choose to use geofence to do it.
I built a foreground to detect when the user moves, even when his application is not open, but i can't get any event through the broadcast, even the initial trigger it cant hear, i am testing manually using fake gps app.
foreground service:
class LocationForegroundService : Service() {
private lateinit var geofencingClient: GeofencingClient
private lateinit var locationClient: FusedLocationProviderClient
val trackingWorking: Boolean get() = geoAlreadyInitialized
private val pendingBroadcastIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceReceiver::class.java)
PendingIntent.getBroadcast(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
companion object {
private var geoAlreadyInitialized = false
private const val GEOFENCE_ID = "rider_location"
private const val TRACKING_CHANNEL = "tracking_channel"
private const val TRACKING_NOTIFICATION_ID = 1
private var count = 0
fun startService(context: Context) {
val startIntent = Intent(context, LocationForegroundService::class.java)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, LocationForegroundService::class.java)
context.stopService(stopIntent)
}
}
override fun onCreate() {
super.onCreate()
locationClient = LocationServices.getFusedLocationProviderClient(this)
geofencingClient = LocationServices.getGeofencingClient(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
...
getLastLocation()
return START_NOT_STICKY
}
#SuppressLint("MissingPermission")
private fun getLastLocation() {
if (geoAlreadyInitialized) geofencingClient.removeGeofences(pendingBroadcastIntent)
locationClient.lastLocation
.addOnSuccessListener { location ->
createGeofence(location)
++count
}
.addOnFailureListener {
print("fail")
// TODO WHEN NOTIFICATION IS BIND TO THE FRAGMENT SET TO PAUSE
}
}
private fun createGeofence(location: Location) {
val geofence = getGeofence(location)
val geofencingRequest = getGeofenceRequest(geofence)
createGeofenceEventListeners(geofencingRequest)
}
#SuppressLint("MissingPermission")
private fun createGeofenceEventListeners(geofencingRequest: GeofencingRequest) {
geofencingClient.addGeofences(geofencingRequest, pendingBroadcastIntent)
.addOnSuccessListener {
geoAlreadyInitialized = true
}
.addOnFailureListener {
// TODO
}
}
private fun getGeofenceRequest(geofence: Geofence) =
GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence)
.build()
private fun getGeofence(it: Location) = Geofence.Builder()
.setCircularRegion(it.latitude, it.longitude, 5F)
.setRequestId(GEOFENCE_ID)
.setTransitionTypes(GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(NEVER_EXPIRE)
.setNotificationResponsiveness(0)
.build()
private fun createNotificationChannel() {...}
override fun onBind(intent: Intent?): IBinder? = null
class GeofenceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
return
}
when (geofencingEvent.geofenceTransition) {
GEOFENCE_TRANSITION_EXIT -> {
context?.showShortToast("EXIT geo blabla")
}
GEOFENCE_TRANSITION_ENTER -> {
context?.showShortToast("ENTER geo blabla")
}
GEOFENCE_TRANSITION_DWELL -> {
context?.showShortToast("dwell geo blabla")
}
}
}
}
}
Manifest
<receiver android:name=".util.LocationForegroundService$GeofenceReceiver"/>
to be more precise the broadcast is never called
Related
I am working on a music player and I want to create a foreground service for notification. But when I use the startForeground() method then it gives me the following error:-
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.aashushaikh.musicplayer, PID: 19998
android.app.RemoteServiceException: Bad notification for startForeground
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2005)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
How can I resolve this error?
I have tried the following solutions but none worked for me
android.app.RemoteServiceException: Bad notification for startForeground when trying to send notification from a service
android.app.RemoteServiceException: Bad notification for startForeground
ApplicationClass.kt:-
class ApplicationClass: Application() {
companion object{
const val CHANNEL_ID = "channel1"
const val PLAY = "play"
const val NEXT = "next"
const val PREVIOUS = "previous"
const val EXIT = "exit"
const val NOTIFICATION_ID = 0
}
override fun onCreate() {
super.onCreate()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//Creating the channel for the notification
val notificationChannel = NotificationChannel(CHANNEL_ID, "Now Playing Song", NotificationManager.IMPORTANCE_HIGH)
notificationChannel.description = "This is an Important Channel for showing the songs"
notificationChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
//Manager is necessary to create the notification channel
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
}
}
}
MusicService.kt:-
val notification = NotificationCompat.Builder(baseContext, ApplicationClass.CHANNEL_ID)
.setContentTitle(PlayerActivity.musicListPlayerActivity[PlayerActivity.songPosition].title)
.setContentText(PlayerActivity.musicListPlayerActivity[PlayerActivity.songPosition].artist)
.setSmallIcon(R.drawable.ic_playlist)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.splash_screen))
.setStyle(androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(mediaSession.sessionToken))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.addAction(R.drawable.ic_previous, "Previous", null)
.addAction(R.drawable.ic_pause, "Pause", null)
.addAction(R.drawable.ic_next, "Next", null)
.addAction(R.drawable.ic_exit, "Exit", null)
.build()
startForeground(1, notification)
And I am calling this showNotification() method in the PlayerActivity.kt:- (SEE THE Method onServiceConnected and onServiceDisconnected in this file which are at the end of this file)
class PlayerActivity : AppCompatActivity(), ServiceConnection {
private lateinit var binding: ActivityPlayerBinding
companion object{
lateinit var musicListPlayerActivity: ArrayList<Music>
var songPosition: Int = 0
var isPlaying: Boolean = false
var musicService: MusicService? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.coolPink)
binding = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(binding.root)
//Starting the service
val intent = Intent(this, MusicService::class.java)
bindService(intent, this, BIND_AUTO_CREATE)
startService(intent)
initializeLayout()
binding.playPauseBtnPA.setOnClickListener {
if(isPlaying == true){
pauseMusic()
}else{
playMusic()
}
}
binding.previousBtnPA.setOnClickListener {
prevNextSong(false)
}
binding.nextBtnPA.setOnClickListener {
prevNextSong(true)
}
}
private fun setLayout(){
Glide.with(this).load(musicListPlayerActivity[songPosition].artUri)
.apply (RequestOptions.placeholderOf(R.drawable.ic_noimage)) .into(binding.imagePlayer)
binding.songNamePlayer.text = musicListPlayerActivity[songPosition].title
}
private fun createMediaPlayer(){
try {
musicService?.mediaPlayer?.let {
musicService!!.mediaPlayer!!.reset()
musicService!!.mediaPlayer!!.setDataSource(musicListPlayerActivity[songPosition].path)
musicService!!.mediaPlayer!!.prepare()
musicService!!.mediaPlayer!!.start()
isPlaying = true
} ?: kotlin.run {
musicService!!.mediaPlayer = MediaPlayer()
}
} catch (e: Exception) {
return
}
}
private fun initializeLayout(){
songPosition = intent.getIntExtra("index", 0)
when(intent.getStringExtra("class")){
"MusicAdapter" -> {
musicListPlayerActivity = ArrayList()
musicListPlayerActivity.addAll(MainActivity.musicListMA)
setLayout()
}
"MainActivity" -> {
musicListPlayerActivity = ArrayList()
musicListPlayerActivity.addAll(MainActivity.musicListMA)
musicListPlayerActivity.shuffle()
setLayout()
}
}
}
private fun playMusic(){
binding.playPauseBtnPA.setIconResource(R.drawable.ic_pause)
isPlaying = true
musicService!!.mediaPlayer!!.start()
}
private fun pauseMusic(){
binding.playPauseBtnPA.setIconResource(R.drawable.ic_play)
isPlaying = false
musicService!!.mediaPlayer!!.pause()
}
private fun prevNextSong(increment: Boolean){
if(increment){
setSongPosition(true)
setLayout()
createMediaPlayer()
}else{
setSongPosition(false)
setLayout()
createMediaPlayer()
}
}
private fun setSongPosition(increment: Boolean){
if(increment){
if(musicListPlayerActivity.size - 1 == songPosition){
songPosition = 0
}else{
++songPosition
}
}else{
if(songPosition == 0){
songPosition = musicListPlayerActivity.size - 1
}else{
--songPosition
}
}
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
var binder = service as MusicService.MyBinder
musicService = binder.currentService()
createMediaPlayer()
musicService!!.showNotification()
}
override fun onServiceDisconnected(p0: ComponentName?) {
musicService = null
}
}
I am using a BroadcastReceiver to send a broadcast to restart the FirebaseMessagingService when the service is destroyed. However, when starting service this way, the FirebaseMessagingService gets immediately destroyed. When the service is destroyed, I will send another broadcast to the receiver and the loop continues indefinitely.
My code for FirebaseMessagingService.
#AndroidEntryPoint
class MessagingService :
FirebaseMessagingService(),
FirebaseAuth.AuthStateListener {
companion object {
var isRunning = false
const val REQUEST_CODE = 0
const val NOTIFICATION_ID = 0
}
#Inject
lateinit var notificationRepository: NotificationRepository
#Inject
lateinit var authRepository: AuthRepository
override fun onCreate() {
super.onCreate()
Log.d("MessagingService", "I am created")
isRunning = true
FirebaseAuth.getInstance().addAuthStateListener(this)
}
override fun onDestroy() {
super.onDestroy()
Log.d("MessagingService", "I am destroyed")
isRunning = false
FirebaseAuth.getInstance().removeAuthStateListener(this)
val intent = Intent()
intent.action = "restartMessagingService"
intent.setClass(this, MessagingServiceRestarter::class.java)
this.sendBroadcast(intent)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val title = remoteMessage.notification?.title
val message = remoteMessage.notification?.body
if (message != null && title != null) {
sendPushNotification(title, message)
}
}
override fun onNewToken(token: String) {
saveTokenToSharedPreferences(token)
authRepository.getUser()?.let {
val uid = it.uid
sendTokenToFirestore(uid, token)
}
}
override fun onAuthStateChanged(auth: FirebaseAuth) {
auth.currentUser?.let {
val uid = it.uid
val savedRegistrationToken =
PreferenceManager.getDefaultSharedPreferences(this)
.getString(getString(R.string.fcm_token_shared_pref_key), "")
savedRegistrationToken?.let { token -> sendTokenToFirestore(uid, token) }
}
}
private fun sendPushNotification(title: String, messageBody: String) {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val channelId = getString(R.string.general_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.mlearn_logo)
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
channelId,
getString(R.string.general_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
private fun saveTokenToSharedPreferences(token: String) {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
with (sharedPref.edit()) {
putString(getString(R.string.fcm_token_shared_pref_key), token)
apply()
}
}
private fun sendTokenToFirestore(uid: String, token: String) {
GlobalScope.launch(Dispatchers.IO) {
notificationRepository.sendNotificationToken(uid, token)
}
}
}
My code for the receiver.
class MessagingServiceRestarter : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("MessagingService", "Restarter" + MessagingService.isRunning.toString())
if (!MessagingService.isRunning) {
context.startService(Intent(context, MessagingService::class.java))
MessagingService.isRunning = true
}
}
}
I start my service every time the MainActivity is created.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MessagingService", "MainActivity" + MessagingService.isRunning.toString())
if (!MessagingService.isRunning) {
val intent = Intent(this, MessagingService::class.java)
startService(intent)
MessagingService.isRunning = true
}
}
override fun onDestroy() {
val intent = Intent()
Log.d("MessagingService", "MainActivity" + MessagingService.isRunning.toString())
intent.action = "restartMessagingService"
intent.setClass(this, MessagingServiceRestarter::class.java)
sendBroadcast(intent)
super.onDestroy()
}
And I have registered my service and receiver on manifest.
<service
android:name=".service.MessagingService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver
android:name=".receiver.MessagingServiceRestarter"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="restartMessagingService"></action>
</intent-filter>
</receiver>
How can I keep my FirebaseMessagingService running? I am using this receiver to restart the service when the service is stopped because the app is cleared from the recent apps.
I would like to make api calls in background and show sometimes notifications due to api data. I had created service which make all calls:
class EndlessService : MyService() {
private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false
override fun onBind(intent: Intent?): IBinder? {
log("Some component want to bind with the service")
// We don't provide binding, so return null
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
log("onStartCommand executed with startId: $startId")
if (intent != null) {
val action = intent.action
log("using an intent with action $action")
when (action) {
Actions.START.name -> startService()
Actions.STOP.name -> stopService()
else -> log("This should never happen. No action in the received intent")
}
} else {
log(
"with a null intent. It has been probably restarted by the system."
)
}
// by returning this we make sure the service is restarted if the system kills the service
return START_STICKY
}
override fun onCreate() {
super.onCreate()
log("The service has been created".uppercase(Locale.ROOT))
startForeground(1, createNotification())
}
override fun onDestroy() {
super.onDestroy()
log("The service has been destroyed".uppercase(Locale.ROOT))
Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show()
}
private fun startService() {
if (isServiceStarted) return
log("Starting the foreground service task")
Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show()
isServiceStarted = true
setServiceState(this, ServiceState.STARTED)
// we need this lock so our service gets not affected by Doze Mode
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire(10*60*1000L /*10 minutes*/)
}
}
// we're starting a loop in a coroutine
GlobalScope.launch(Dispatchers.IO) {
while (isServiceStarted) {
launch(Dispatchers.IO) {
pingFakeServer()
}
delay(10 * 60)
}
log("End of the loop for the service")
}
}
private fun stopService() {
log("Stopping the foreground service")
Toast.makeText(this, "Service stopping", Toast.LENGTH_SHORT).show()
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
}
}
stopForeground(true)
stopSelf()
} catch (e: Exception) {
log("Service stopped without being started: ${e.message}")
}
isServiceStarted = false
setServiceState(this, ServiceState.STOPPED)
}
private fun pingFakeServer() {
}
private fun createNotification(): Notification {
val notificationChannelId = "ENDLESS SERVICE CHANNEL"
// depending on the Android API that we're dealing with we will have
// to use a specific method to create the notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
val channel = NotificationChannel(
notificationChannelId,
"Endless Service notifications channel",
NotificationManager.IMPORTANCE_MIN
).let {
it.description = "Endless Service channel"
it.enableLights(true)
it.lightColor = Color.RED
it.enableVibration(true)
it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
it
}
notificationManager.createNotificationChannel(channel)
}
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder(
this,
notificationChannelId
) else Notification.Builder(this)
return builder
.setContentTitle("Endless Service")
.setContentText("This is your favorite endless service working")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker("Ticker text")
.setPriority(Notification.PRIORITY_MIN) // for under android 26 compatibility
.build()
}
}
then I added all info to the manifest and added classes for this service support:
enum class Actions {
START,
STOP
}
enum class ServiceState {
STARTED,
STOPPED,
}
private const val name = "SPYSERVICE_KEY"
private const val key = "SPYSERVICE_STATE"
fun setServiceState(context: Context, state: ServiceState) {
val sharedPrefs = getPreferences(context)
sharedPrefs.edit().let {
it.putString(key, state.name)
it.apply()
}
}
fun getServiceState(context: Context): ServiceState {
val sharedPrefs = getPreferences(context)
val value = sharedPrefs.getString(key, ServiceState.STOPPED.name)
return ServiceState.valueOf(value!!)
}
private fun getPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences(name, 0)
}
and start it from an activity:
override fun onResume() {
super.onResume()
actionOnService(Actions.STOP)
}
override fun onPause() {
super.onPause()
actionOnService(Actions.START)
}
private fun actionOnService(action: Actions) {
if (getServiceState(this) == ServiceState.STOPPED && action == Actions.STOP) return
Intent(this, EndlessService::class.java).also {
it.action = action.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
log("Starting the service in >=26 Mode")
startForegroundService(it)
return
}
log("Starting the service in < 26 Mode")
startService(it)
}
}
but as I see such way creates empty notification which can't be dismissed. Maybe I can use another way?
I use exoplayer in service for background streaming. And I found something strange. When I get the player at activity, the player is null. But the music is streaming and I can hear that song. Why the player in service is null even though it's streaming? I don't understand this. For example, If I click the musicOnOff Button, I get the null point error of player, but the music is streaming. please let me understand this...
error
kotlin.KotlinNullPointerException
Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var mBound = false
var mIntent = Intent()
var audioService :AudioService ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
audioService = AudioService()
mIntent = Intent(this,AudioService::class.java)
Util.startForegroundService(this,mIntent)
binding.musicOnOffButton.setOnClickListener {
initializePlayer()
}
}
private fun initializePlayer() {
if (mBound) {
val player: SimpleExoPlayer = audioService!!.getplayerInstance()!!
binding.musicPlayer.player = player
binding.musicPlayer.useController = true
}
}
override fun onResume() {
super.onResume()
bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE)
}
override fun onPause() {
super.onPause()
unbindService(mConnection)
}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
mBound = true
//initializePlayer()
}
override fun onServiceDisconnected(componentName: ComponentName) {
}
}
}
Service
class AudioService : Service() {
private val mBinder: IBinder =Binder()
var player: SimpleExoPlayer? = null
private var cacheDataSourceFactory : CacheDataSourceFactory?=null
var mContext: Context? = null
lateinit var notification : Notification
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
override fun onCreate() {
super.onCreate()
Log.d("TAG","TAG : onCreate")
}
private fun releasePlayer() {
if (player != null) {
player!!.release()
player = null
}
}
override fun onBind(intent: Intent?): IBinder {
return mBinder
}
fun getplayerInstance(): SimpleExoPlayer? {
if (player == null) {
startPlayer(mContext!!)
}
return player
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d("TAG","TAG : onStartCommand")
mContext = this
startPlayer(mContext!!)
val myNotificationListener = object :MyNotificationListener{
override fun onPlay() {
player!!.playWhenReady= !player!!.playWhenReady
}
override fun onRemove() {
stopSelf()
//stopForeground(true)
}
}
val receiver = NotificationBroadCast(myNotificationListener)
registerReceiver(receiver, IntentFilter("TRACKS_TRACKS"))
createNotification(
this,
MusicTrack("title 1","artist 1",R.drawable.reaction_happy),
1,
3
)
startForeground(NOTIFICATION_ID,notification)
return START_STICKY
}
private fun startPlayer(context: Context) {
val uri: Uri = Uri.parse("https://storage.googleapis.com/exoplayer-test-media-0/Jazz_In_Paris.mp3")
player = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector())
cacheDataSourceFactory = getCacheDataSourceFactory(context)
val mediaSource: MediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(uri)
player!!.prepare(mediaSource)
player!!.setPlayWhenReady(true)
}
fun getCacheDataSourceFactory(context : Context) : CacheDataSourceFactory?{
if(cacheDataSourceFactory==null){
cacheDataSourceFactory = CacheDataSourceFactory(
context,
DefaultDataSourceFactory(context, "ua"),
MyExoPlayer.MAX_CACHE_VALUE, MyExoPlayer.MAX_CACHE_FILE_VALUE
)
}
return cacheDataSourceFactory
}
fun createNotification(context: Context, musicTrack: MusicTrack, pos:Int, size:Int){
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val resultIntent = Intent(context, MobileActivity::class.java)
resultIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val resultPendingIntent: PendingIntent? = PendingIntent.getActivity(context,0,resultIntent, PendingIntent.FLAG_CANCEL_CURRENT)
var previousIntent :PendingIntent ?=null
var nextIntent :PendingIntent ?=null
var playIntent :PendingIntent ?=null
var image_prev = 0
var image_next = 0
if(pos==0){
previousIntent = null
image_prev =0
}else{
val intentPrevious = Intent(context,
NotificationReceiver::class.java).setAction(
MusicNotification.ACTION_PREVIOUS
)
previousIntent = PendingIntent.getBroadcast(context,0,intentPrevious,PendingIntent.FLAG_UPDATE_CURRENT)
image_prev = R.drawable.play_previous
}
if(pos==size){
nextIntent = null
image_next =0
}else{
val intentNext = Intent(context,
NotificationReceiver::class.java).setAction(
MusicNotification.ACTION_NEXT
)
nextIntent = PendingIntent.getBroadcast(context,0,intentNext,PendingIntent.FLAG_UPDATE_CURRENT)
image_next = R.drawable.play_next
}
val intentPlay = Intent(context,
NotificationReceiver::class.java).setAction(
MusicNotification.ACTION_PLAY
)
playIntent = PendingIntent.getBroadcast(context,0,intentPlay,PendingIntent.FLAG_UPDATE_CURRENT)
val intentCancel = Intent(context,
NotificationReceiver::class.java).setAction(
MusicNotification.ACTION_PLAY
)
val cancelIntent = PendingIntent.getBroadcast(context,0,intentCancel,PendingIntent.FLAG_UPDATE_CURRENT)
var binding = CustomNotificationBinding.inflate(LayoutInflater.from(context))
var contentView = RemoteViews(context.packageName, R.layout.custom_notification)
contentView.setTextViewText(binding.notiMusicName.id,musicTrack.title)
contentView.setOnClickPendingIntent(binding.prevButton.id,previousIntent)
contentView.setOnClickPendingIntent(binding.startButton.id,playIntent)
contentView.setOnClickPendingIntent(binding.nextButton.id,nextIntent)
notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.music_note)
.setOnlyAlertOnce(true)
.setShowWhen(false)
.setOngoing(true)
.setAutoCancel(true)
.setContent(contentView)
.setCustomContentView(contentView)
.setCustomBigContentView(contentView)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(resultPendingIntent)
.setColorized(true)
.setColor(context.getColor(R.color.colorPrimary))
.build()
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.O){
val channelName ="Music Channel"
val channel = NotificationChannel(CHANNEL_ID,channelName, NotificationManager.IMPORTANCE_DEFAULT)
channel.enableLights(true)
channel.lightColor= 0x00FFFF
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
}
}
}
I solved this. because of my insufficient understanding about service and binder, I can't get the result I want.
I added Local Binder that point the current Service
added in AudioService
val mBinder: IBinder = LocalBinder()
inner class LocalBinder : Binder() {
val service: AudioService
get() = this#AudioService
}
added In MainActivity
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
mBound = true
val binder = iBinder as AudioService.LocalBinder
audioService = binder.service
initializePlayer()
}
override fun onServiceDisconnected(componentName: ComponentName) {
}
}
java.lang.InterruptedException error is thrown onDestroy because of the Thread.sleep in the Runnable. I thought that the Runnable created a new Thread and allowed for sleeps?
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var startBtn: Button
lateinit var stopBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startBtn = findViewById(R.id.startButton)
stopBtn = findViewById(R.id.stopButton)
startBtn.setOnClickListener { startService() }
stopBtn.setOnClickListener { stopService() }
}
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
ContextCompat.startForegroundService(this, serviceIntent)
}
private fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
}
ForegroundService.kt
class ForegroundService : Service() {
private lateinit var thread: Thread
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("channel_service", "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
val notification: Notification = Notification.Builder(this, "channel_service")
.setContentTitle("Title")
.setContentText("Text")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
startForeground(1, notification)
runnable = TestRunnable()
thread = Thread(runnable)
thread.start()
}
return START_NOT_STICKY
}
override fun onDestroy() {
if(thread.isAlive) {
thread.interrupt()
}
super.onDestroy()
}
override fun onBind(intent: Intent): IBinder? {
return null
}
class TestRunnable : Runnable {
override fun run() {
for(i in 1..15) {
Log.d("Debug", "startThread " + i)
Thread.sleep(1000) //exception through here onDestroy
}
}
}
}
Use thread.interrupt() to interrupt the thread. Add try-catch around Thread.sleep(1000) call:
class TestRunnable : Runnable {
override fun run() {
var isInterrupted = false
for(i in 1..15) {
Log.d("Debug", "startThread " + i)
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
isInterrupted = true
}
if (isInterrupted) break
}
}
}
You can also use some AtomicBoolean variable to break the loop in Runnable implementation:
class TestRunnable : Runnable {
private val isStopped = AtomicBoolean(false)
fun stop() {
isStopped.set(true)
}
override fun run() {
for(i in 1..15) {
if (isStopped.get()) break
// ...
}
}
}
override fun onDestroy() {
if(thread.isAlive) {
runnable.stop()
thread.interrupt()
}
super.onDestroy()
}
Add thread = null; after Thread(runnable).interrupt(). This should work. If you want it to be killed when the user stops the activity you should try overriding onStop instead of onDestroy().
Hope it helps! Happy Coding:)