i'm writing music app. I have service with MediaPlayer which shows notification with custom view (play, next, back buttons). onClick reaction for these buttons was implemented using broadcasting. Is there a way to get access to MediaPlayer object from service, to use it in my broadcast receiver class? Or should I use bind service?
My Receiver class:
class PlayerNotificationReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent != null) {
val intentAction = intent.action
when(intentAction){
"PLAY_ACTION" -> {
// HOW TO GET ACCESS TO MEDIA PLAYER IN SERVICE ?????
Toast.makeText(context, "PLAY_ACTION", Toast.LENGTH_SHORT).show()
// TODO Change image button image in fragment
}
"BACK_ACTION" -> {
// TODO Notification back button implementation
Toast.makeText(context, "BACK_ACTION", Toast.LENGTH_SHORT).show()
}
"NEXT_ACTION" -> {
// TODO Notification next button implementation
Toast.makeText(context, "NEXT_ACTION", Toast.LENGTH_SHORT).show()
}
"PAUSE_ACTION" -> {
// TODO Notification pause button implementation
Toast.makeText(context, "PAUSE_ACTION", Toast.LENGTH_SHORT).show()
}
}
}
}
}
My Service class:
class PlayerService: Service() {
private var song: Int = 0
var songDuration: Long = 0
private lateinit var playerNotificationReceiver: PlayerNotificationReceiver
private val intentRequestCode: Int = 0
private var mediaPlayer: MediaPlayer = MediaPlayer()
private var isPlaying: Boolean = false
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Broadcast receiver
playerNotificationReceiver = PlayerNotificationReceiver()
// Registering broadcast receiver
registerReceiver(playerNotificationReceiver, IntentFilter(getString(R.string.PLAY_ACTION)))
registerReceiver(playerNotificationReceiver, IntentFilter(getString(R.string.PAUSE_ACTION)))
registerReceiver(playerNotificationReceiver, IntentFilter(getString(R.string.BACK_ACTION)))
registerReceiver(playerNotificationReceiver, IntentFilter(getString(R.string.NEXT_ACTION)))
song = intent?.getIntExtra("song", 0) ?: R.raw.taco_hemingway_europa
// onStartCommand implementation
if (!isPlaying) {
// Creating pending intent for notification
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
intentRequestCode,
intent,
PendingIntent.FLAG_IMMUTABLE
)
// Pending intent with broadcast for custom view back button on click
val nextIntent = Intent(getString(R.string.NEXT_ACTION))
nextIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val nextPendingIntent = PendingIntent.getBroadcast(this, 0, nextIntent, PendingIntent.FLAG_IMMUTABLE)
// Pending intent with broadcast for custom view back button on click
val backIntent = Intent(getString(R.string.BACK_ACTION))
backIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val backPendingIntent = PendingIntent.getBroadcast(this, 0, backIntent, PendingIntent.FLAG_IMMUTABLE)
// Pending intent with broadcast for custom view back button on click
val playIntent = Intent(getString(R.string.PLAY_ACTION))
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val playPendingIntent = PendingIntent.getBroadcast(this, 0, playIntent, PendingIntent.FLAG_IMMUTABLE)
// Remote view
val playerNotificationLayout = RemoteViews(packageName, R.layout.player_small_notification)
val playerNotificationLayoutExpanded = RemoteViews(packageName, R.layout.player_large_notification)
// Building notification with custom view
val notificationBuilder = NotificationCompat.Builder(this, PLAYER_CHANNEL)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.app_notification_icon)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(playerNotificationLayout)
.setCustomBigContentView(playerNotificationLayoutExpanded)
.setContentTitle("Title")
.setContentText("Name")
// Setting custom view button on click reaction
playerNotificationLayout.setOnClickPendingIntent(R.id.notificationPlayButton, playPendingIntent)
playerNotificationLayout.setOnClickPendingIntent(R.id.notificationBackButton, backPendingIntent)
playerNotificationLayout.setOnClickPendingIntent(R.id.notificationNextButton, nextPendingIntent)
// Notification manager
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Creating notification
val notification = notificationBuilder.build()
// Post a notification to be shown in the status bar.
notificationManager.notify(1, notification)
// Creating media player and starting music - service main task
mediaPlayer = MediaPlayer.create(this, song)
//mediaPlayer.start()
//isPlaying = true
//songDuration = mediaPlayer.duration.toLong()
// Starting Service
startForeground(1, notification)
}
return Service.START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
// Stop playing music and set isPlaying to false
mediaPlayer.stop()
isPlaying = false
// Unregistering broadcast receiver
unregisterReceiver(playerNotificationReceiver)
}
My fragment class:
class PlayerFragment : Fragment() {
private lateinit var playerServiceIntent: Intent
private var isPlaying: Boolean = false
// Animation lazy initalization
private val rotateAnimation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.player_button_rotation) }
// Binding
private var _binding: FragmentPlayerBinding? = null
private val binding
get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// playerServiceIntent
playerServiceIntent = Intent(activity , PlayerService::class.java)
playerServiceIntent.putExtra("song", R.raw.taco_hemingway_europa)
// Starting service
requireActivity().startService(playerServiceIntent)
}
override fun onResume() {
super.onResume()
}
override fun onPause() {
super.onPause()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentPlayerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// playerServiceIntent
playerServiceIntent = Intent(activity , PlayerService::class.java)
playerServiceIntent.putExtra("song", R.raw.taco_hemingway_europa)
// Starting service
requireActivity().startService(playerServiceIntent)
binding.playerPlayButton.setOnClickListener {
if(isPlaying){
// Pending intent with broadcast for custom view play button on click
val playIntent = Intent(getString(R.string.PLAY_ACTION))
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val backPendingIntent = PendingIntent.getBroadcast(context, 0, playIntent, PendingIntent.FLAG_IMMUTABLE)
binding.playerPlayButton.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.pause_button, null))
} else {
// Pending intent with broadcast for custom view pause button on click
val pauseIntent = Intent(getString(R.string.PAUSE_ACTION))
pauseIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val backPendingIntent = PendingIntent.getBroadcast(context, 0, pauseIntent, PendingIntent.FLAG_IMMUTABLE)
binding.playerPlayButton.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.play_button, null))
// TODO Start animation
//binding.playerPlayButton.startAnimation(rotateAnimation)
}
}
// TODO Time bar implementation
/*
Timer implementation
val timer = Timer()
if(mediaPlayer != null && mediaPlayer.isPlaying){
timer.scheduleAtFixedRate(timerTask {
currentPosition = mediaPlayer.currentPosition.toLong()
binding.textView.text = currentPosition.toString()
},0,1000)
} else {
timer.cancel()
timer.purge()
}
*/
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
A BroadcastReceiver is short lived. You cannot bind to a Service in a BroadcastReceiver, as binding is an asynchronous process and your BroadcastReceiver will be gone before the binding completes.
You can request that the Service do something by calling startService() with an Intent that has some ACTION in it that tells the Service what you want to do.
Or you can just change your notification so that the actions go directly to the Service instead of going via your BroadcastReceiver.
As an alternative, (this is kinda hacky), you could store the MediaPlayer refrerence in a public static (global) variable and then you could use it directly from your BroadcastReceiver. This is not the recommended approach, but it does work.
Related
I have a foreground service. It does some async work in the background and periodically issues a notification asking the user if the work should be stopped.
The notification has a button "Yes, please" and when clicked it must invoke stopAction method.
The code below is where I'm stuck. I'm maybe way off and this can't be done. Any advice?
MainService.kt
...
override fun onCreate() {
subscribeToStopActionRequest()
}
private fun subscribeToStopActionRequest () {
var eventReceiverHelper = EventReceiverHelper { stopAction() }
val filter = IntentFilter().apply {
addAction("${packageName}.stop_action_request")
}
registerReceiver(eventReceiverHelper, filter)
}
private fun stopAction () {
...
}
private fun showNotification () {
val intent = Intent(this, EventService::class.java)
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
var notification = NotificationCompat.Builder(this, state.notificationChannelId)
.setContentTitle("Want to stop?")
.addAction(R.drawable.stop_icon, "Yes, please", pendingIntent)
.build()
with(NotificationManagerCompat.from(this)) {
notify(1, notification)
}
}
Event receiver helper
class EventReceiverHelper(val cb: () -> Unit): BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
cb()
}
}
Define a constant:
private const val EXTRA_STOP = "stop";
Then create an intent for your service and put an extra flag:
val intent = Intent(context, YourService::class.java);
intent.putExtra(EXTRA_STOP, true);
Now you can create a pending intent as your handler:
val pendingIntent: PendingIntent = PendingIntent.getService(context, YOUR_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)
This pending intent will trigger the onStartCommand method on your service, where you can check whether the stop flag was set.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.getBooleanExtra(EXTRA_STOP, false)) {
stopAction()
}
...
}
There a function in my intent service that works like a countdown. It is called counter.
What should be added to IntentService or directly into counter to stop this loop after some action in MainActivity?
class IntentServiceExample : IntentService("Loop_test") {
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object {
val PARAM_OUT_MSG = "None"
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText("kylsha")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return super.onStartCommand(intent, flags, startId)
}
override fun onHandleIntent(p0: Intent?) {
Toast.makeText(this,"Service started",Toast.LENGTH_LONG).show()
val broadcastIntent = Intent()
broadcastIntent.action = "com.example.intenttest.action.RESPONSE"
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
sendBroadcast(broadcastIntent)
counter(broadcastIntent)
}
fun counter(bc: Intent){
for (i in 1..100){
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
}
}
create a variable in the class.
Create a setter to set the variable true.
In you rcounter routine, check for the variable being set.
private val cancelCounter = false
public fun setToCancel() {
cancelCounter = true
}
/*Stuff*/
fun counter(bc: Intent){
for (i in 1..100){
if (cancelCounter) {
cancelCounter = false
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
d("number", i.toString())
sendBroadcast(bc)
}
}
You may not have direct access to the object from main - if not then you should create this class with a singleton pattern ;)
I don't code in kotlin enough to now the "right way" to do it, but some links to the right way:
https://blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin
https://medium.com/swlh/singleton-class-in-kotlin-c3398e7fd76b
Both of these links have some information about why they make the decisions they make in the structure pattern, and some of how the code behind for the implementations works too ;)
For someone who also learn Kotlin I will post my solution as well. It looks pretty simple, however, there probably could be more solutions.
I created a simple Kotlin object like:
object Trigger {
var triggerStop = 0
fun getTrigger(): Int{
return triggerStop
}
}
As you can see variable triggerStop can be changed and called with function getTrigger()
So I added this object into MainActivity to buttons' setOnClickListeners:
class MainActivity : AppCompatActivity() {
lateinit var i:Intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
i = Intent(this, IntentServiceExample::class.java)
buttonStart.setOnClickListener{
Trigger.triggerStop = 0 // this variable will be checked in IntentService
startService(i)
}
buttonEnd.setOnClickListener{
Trigger.triggerStop = 1 // this variable will be checked in IntentService
stopService(i)
}
}
}
Then I put this object into my IntentService. In a loop that I want to be stopped by user interaction I put a check like in #Watachiaieto's answer.
fun counter(bc: Intent){
for (i in 1..100){
val stopIt = Trigger.getTrigger() // get trigger value
if (stopIt == 1) {
break
}
bc.putExtra(PARAM_OUT_MSG, i.toString())
Thread.sleep(1000)
sendBroadcast(bc)
}
}
I have an app that uses Service class to perform task in foreground.
This service also contains a Handler object to run same function multiple times. I want to change attributes in my activity_main.xml while functions are running in Service. For example when function calculates something in Service the result prints in TextView.
How it would be correct access activity_main's objects to retrieve and change their values and attributes?
Here is what I have:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private var notificationManager: NotificationManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonStart.setOnClickListener{
buttonStart.isEnabled = false
buttonStop.isEnabled = true
IdListener.startService(this, "Foreground Service is running...")
}
}
}
IdListener.kt:
class IdListener : Service() {
private val CHANNEL_ID = "ForegroundService Kotlin"
private lateinit var mainHandler: Handler
private lateinit var mRunnable: Runnable
companion object {
fun startService(context: Context, message: String) {
val startIntent = Intent(context, IdListener::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, IdListener::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mainHandler = Handler()
mRunnable = Runnable { showRandomNumber(tm) }
mainHandler.postDelayed(mRunnable, 1000)
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText(input)
.setSmallIcon(R.drawable.ic_notofication)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
mainHandler.removeCallbacks(mRunnable)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
/// function in which I want elements from activity_main.xml to be changed
fun showRandomNumber(manager: TelephonyManager){
myTextView.text = "Working..."
mainHandler.postDelayed(mRunnable, 1000)
}
}
Here's how I'd probably handle your case. I don't know exactly what you're doing, but I'm just having the text view show "Working..." when it starts the service until there's an ID available. I haven't tested this and haven't worked with services in a long time, so you might want other input.
object IdServiceData {
val id = MutableLiveData<String>()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...
myTextView.text = "Working..." // should really use string resource here.
IdServiceData.id.observe(this) {
myTextView.text = it.value
}
}
}
When an Activity or Fragment observes a LiveData, they automatically stop observing when they are destroyed, so they are not leaked. So your Activity can be destroyed and recreated multiple times while the Service is running and it will keep getting the proper updates.
class IdListener : Service() {
//...
private fun broadcastNewId(id: String){
mainHandler.post {
IdServiceData.id.value = id
}
}
}
If you want better encapsulation, I suppose you could abstract out the MutableLiveData by creating a separate IdServiceDataProvider that has the MutableLiveData and is used by the service, and the IdServiceData would reference the data like this: val id: LiveData<String> = IdServiceDataProvider.id
Im working on an android app which uses notifications frequently.
I chose to set the alarms via setting activity rather then Main Activity but I couldn't manage to find out how to cancel the Alarm Manager via the settingActivity immediately after pressing the switch.
I only found out how to cancel the notifications on the MainActivity, which stops them only after closing and opening the app.
what is the preferred way to do it?
from SettingActivity.kt:
class NotificationPreferenceFragment : PreferenceFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_notification)
setHasOptionsMenu(false)
var switchPref: Preference = findPreference(getString(R.string.pref_notifications_switch_key))
switchPref.onPreferenceChangeListener = OnPreferenceChangeListener { preference, isChecked ->
var toast: Toast = if (isChecked as Boolean) {
Toast.makeText(activity, "switch is ON", Toast.LENGTH_SHORT)
} else {
Toast.makeText(activity, "notifications is OFF", Toast.LENGTH_SHORT)
}
toast.show()
true
}
}
cancel method from MainActivity:
fun cancelAlarm() {
alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent1 = Intent(applicationContext, AlarmReceiver::class.java)
alarmIntent = PendingIntent.getBroadcast(applicationContext, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT)
alarmMgr!!.cancel(alarmIntent)
}
As said by a commenter, just use BroadcastReceiver
NotificationPreferenceFragment.java
class NotificationPreferenceFragment : PreferenceFragment() {
val broadcaster: LocalBroadcastManager? = null;
override fun onCreate(savedInstanceState: Bundle?) {
broadcaster = LocalBroadcastManager.getInstance(this);
}
// Inside your onPreferenceChangeListener, depends on when you want to call it, either ON or OFF
Intent intent = new Intent("YOUR_DATA_STRING");
intent.putExtra(ANY_EXTRAS_STRING, DATA_ITSELF);
broadcaster.sendBroadcast(intent);
}
MainActivity.java
import android.support.v4.content.LocalBroadcastManager;
override fun onStart() {
super.onStart()
LocalBroadcastManager.getInstance(this).registerReceiver(MYReceiver,
IntentFilter("YOUR_DATA_STRING")
)
}
private val MYReceiver = object : BroadcastReceiver() {
fun onReceive(context: Context, intent: Intent) {
if (intent.extras != null) {
// get any extras if neccessary
// intent.extras!!.getString("ANY_EXTRAS_STRING")
cancelAlarm()
}
}
}
override fun onStop() {
super.onStop()
LocalBroadcastManager.getInstance(this).unregisterReceiver(MYReceiver)
}
I added a button inside a notification
but I don't know how to have it call a function when it's clicked.
I tried an approach like this https://code.google.com/p/languagepickerwidget/source/browse/trunk/trunk/src/org/gnvo/langpicker/LangPicker.java since it's also using a RemoteViews object but nothing happens when I click the button.
This is what I currently have:
private void createNotification(){
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager) getSystemService(ns);
Notification notification = new Notification(R.drawable.ic_launcher, null, System.currentTimeMillis());
RemoteViews notificationView = new RemoteViews(getPackageName(), R.layout.notification_switch);
//the intent that is started when the notification is clicked (works)
Intent notificationIntent = new Intent(this, SettingsActivity.class);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.contentView = notificationView;
notification.contentIntent = pendingNotificationIntent;
notification.flags |= Notification.FLAG_NO_CLEAR;
//this is the intent that is supposed to be called when the button is clicked
Intent switchIntent = new Intent(this, switchButtonListener.class);
PendingIntent pendingSwitchIntent = PendingIntent.getBroadcast(this, 0, switchIntent, 0);
notificationView.setOnClickPendingIntent(R.id.buttonswitch, pendingSwitchIntent);
notificationManager.notify(1, notification);
}
public static class switchButtonListener extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("TAG", "test");
}
}
I can start an activity with the button but I didn't succeed to have it call a simple function. What would be the best way to do this?
Edit:
I found out that I had to register "switchButtonListener" in AndroidManifest.xml
<receiver android:name="SettingsActivity$switchButtonListener" />
Source: Android Activity with no GUI
It works now.
I found out that I had to register "switchButtonListener" in AndroidManifest.xml
<receiver android:name="SettingsActivity$switchButtonListener" />
Source: Android Activity with no GUI
Later I found out that I can also use code like this to achieve the same thing without modifying the manifest.
switchButtonListener = new SwitchButtonListener();
registerReceiver(switchButtonListener, new IntentFilter(SWITCH_EVENT));
.
public class switchButtonListener extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("TAG", "test");
}
}
.
Intent switchIntent = new Intent(LangService.SWITCH_EVENT);
PendingIntent pendingSwitchIntent = PendingIntent.getBroadcast(context, 0, switchIntent, 0);
notificationView.setOnClickPendingIntent(R.id.buttonswitch, pendingSwitchIntent);
Note that this way I can declare the switchButtonListener class without the static attribute (if not static, it would crash in the previous example) giving me much more flexibility.
Don't forget to call unregisterReceiver() later.
In Kotlin you can register a receiver with an anonymous class.
const val STOP_ALARM_ACTION = "STOP_ALARM"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
registerReceiver(object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
stopAlarm();
}
}, IntentFilter(STOP_ALARM_ACTION))
}
private fun playAlarm() {
ringtone.stop()
val stopIntent = Intent(STOP_ALARM_ACTION)
val stopPendingIntent = PendingIntent.getBroadcast(this, 0, stopIntent, 0)
val notification = NotificationCompat.Builder(this, "Timer1_ALARM")
// ...
.addAction(android.R.drawable.ic_delete, "Stop", stopPendingIntent)
.build()
// ...
}
private fun stopAlarm() {
ringtone.stop()
}
}
I believe it is important to also unregister action. So my way of writing this nicely is:
val playButtonAction = register("play_button_action") {
main.looper?.player?.asStarted { it.stop() }
}
so you can do:
override fun onDestroy() {
super.onDestroy()
unregister(playButtonAction)
}
using:
fun Context.register(action: String, function: () -> void): BroadcastReceiver =
register(IntentFilter(action)) { _, _ -> function() }
fun Context.register(intent: IntentFilter,
function: (Intent, BroadcastReceiver) -> void): BroadcastReceiver {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = function(intent, this)
}
registerReceiver(receiver, intent)
return receiver
}
fun Context.unregister(receiver: BroadcastReceiver) {
unregisterReceiver(receiver)
}
And also u use playButtonAction:
val stopIntent = PendingIntent.getBroadcast(this, 0, Intent("play_button_action"),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE);
This is my complete Service class:
class LooperPlayNotificationService : Service() {
companion object {
val NOTIFICATIONS_CHANNEL = "${app.packageName} notifications"
}
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
start()
return START_STICKY
}
override fun onCreate() {
super.onCreate()
start()
}
private val playButtonActionId = "play_button_action"
private lateinit var playButtonAction: BroadcastReceiver
private var started = false
// https://stackoverflow.com/questions/6619143/start-sticky-foreground-android-service-goes-away-without-notice
// There's a bug in 2.3 (not sure if it was fixed yet) where when a Service is killed and restarted,
// its onStartCommand() will NOT be called again. Instead you're going to have to do any setting up in onCreate()
private fun start() {
if (started) return
started = true
startForeground(647823876, createNotification())
playButtonAction = register(playButtonActionId) {
main.looper?.player?.asStarted { it.stop() }
}
}
override fun onDestroy() {
super.onDestroy()
unregister(this.playButtonAction)
}
private fun createNotification() = Builder(this, NOTIFICATIONS_CHANNEL)
.setSmallIcon(outline_all_inclusive_24)
.setContentIntent(getActivity(this, 0, Intent<InstrumentsActivity>(this),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setPriority(PRIORITY_DEFAULT)
.setAutoCancel(false).setOngoing(true)
.addAction(ic_stop_circle_black_24dp, "Stop",
getBroadcast(this, 0, Intent(playButtonActionId),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setContentText(getString(R.string.app_name))
.setContentText(main.looper?.preset?.item?.value?.title?.value).build()
}