I am implementing a music player. The notifications allow the user to pause or skip a song.
I use
Intent i = new Intent("com.package.app");
mExpandedView.setOnClickPendingIntent(R.id.next_song, PendingIntent.getBroadcast(this, 0, i, 0));
In order to transmit this click to the MusicService that hosts the MediaPlayer and all the associated methods. I would like to directly call a method part of this service (playNextSong() for example) but getService() seems to only allow me to launch a new service, not to call a method in the service, or get some data. I don't even need to launch the service, since the music is playing, it is already running.
So is there a way to do this that I am not aware of ?, or is :
Notification broadcasts to BroadcastReceiver, then BroadcastReceiver broadcasts to the service the recommended way do accomplish this action ?
It looks like a convoluted way to do something simple...
Create PendingIntent for notification as broadcast message, custom one (use your own string like com.my.custom.broadcast.message.action). Create and register in AndroidManifest new broadcast receiver that will be fired by this custom action. OnReceive method of the Broadcast receiver, start your service with custom arguments/action or whatever, based on class of Service and context arguments passed into onReceive method.
Probably you can try to directly start service by creating PendingIntent for that, but I think it is better do it through middle-step: BroadcastReceiver
From the Notification you can start an Activity. That activity would do "bindService" and call the appropriate method in the service, then finish(). The activity doesn't need to have a UI, so the user won't see it. But that's even more code than a Broadcastreceiver.
Related
I want notification manager to show a popup which has an 'X' button to close it.
When closed, I want the BroadcastReceiver to invoke a method on the Service which had registered the receiver and notification, and is the container.
RemoteViews remoteView = createPopupView();
Intent intent = new Intent(myService, MyReceiver.class);
intent.setAction(CLOSE_BUTTON_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(myService,
MY_POPUP_ID, intent, 0);
remoteView.setOnClickPendingIntent(R.id.img_close_selector, pendingIntent);
builder.setContent(remoteView);
I found that MyReceiver had to be statically defined in the manifest.
When I tried to dynamically register the receiver, it was not called at all when notification was fired.
But then I also found that my Receiver could not invoke any methods in myService because trying to cast context in onReceive(),
((MyService)context).foo();
or
((MyService) getApplicationContext()).foo()
causes...
AndroidRuntime: java.lang.RuntimeException:
Unable to start receiver com.myco.MyClass$MyReceiver: java.lang.ClassCastException:
android.app.ReceiverRestrictedContext cannot be cast to com.myco.MyService
I suppose I could fire another intent from BroadcastReceiver, but it seems like another relay race - One BroadcastReceiver hooked to another BroadcastReceiver. Also I heard that broadcasts can be delayed.
So how does my BroadcastReceiver communicate with the Service?
When I tried to dynamically register the receiver, it was not called at all when notification was fired.
I am assuming that this Notification is for a foreground service. If so, a dynamically-registered receiver should work, if your Intent matches your IntentFilter, though you may need to call setPackage() on the Intent to get past the implicit broadcast ban on Android 8.0+.
But then I also found that my Receiver could not invoke any methods in myService because trying to cast context in onReceive()
The Context passed to onReceive() will be unrelated to any other component of your app.
So how does my BroadcastReceiver communicate with the Service?
If the Notification should only exist when the service is running, you should switch back to the dynamic registration approach. Or, use a getService() version of PendingIntent to talk directly to your Service. A getService() PendingIntent will trigger onStartCommand() on your Service, and you can put stuff in the Intent to tell you what to do, such as your setAction(CLOSE_BUTTON_ACTION) call. The Intent will need to identify your service instead of identifying a BroadcastReceiver, though.
If the Notification might exist when the service is not running, then either:
Use the getService() PendingIntent that I mentioned above, or
Use startService() from onReceive() of your BroadcastReceiver to start the service (if it is not already started) and trigger onStartCommand() (for you to do whatever it is that you are supposed to be doing)
Is it possible to send an intent from a service to an Application class? Not Activity?
I wouldn't know what activity would be running at a particular time, so I am adding a boolean flag in the activity class that detects the activity and sends the appropriate data based on the broadcast received.
If your Service is active, then your Application class is active as well.
Otherwise you wouldn't be able to use getApplicationContext().
Although I'm skeptic about a service that runs forever there is a very clean way to make the Service communicate with a certain Activity, should the last one be currently active.
Such clean way is called LocalBroadcastManager.
The Activity meant to receive the data should register a BroadcastReceiver in onResume() and unregister it in onPause().
You instantiate your BroadcastReceiver in your Activity's onCreate()
this.localBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Do what you have to do here if you receive data from the Service.
}
}
You create a Filter so your Activity only listens to a certain type of signals.
private IntentFilter notifIntentFilter new IntentFilter("com.you.yourapp.MY_SIGNAL");
in onResume()
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(this.localBroadcastReceiver, notifIntentFilter);
in onPause()
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(this.localBroadcastReceiver);
Now whenever you want to send data to your Activity, your Service can call:
final Intent intent = new Intent();
intent.setAction("com.you.yourapp.MY_SIGNAL");
// put your data in intent
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
If your Activity is awake, it will respond to the signal. Otherwise, if it's in the background, or it is not instantiated it won't.
You can apply this pattern to as many Activities as you wish.
Still, I have never used this inside the Application class. But you can try to register your receiver there. It might work, since if the Application class is destroyed, the BroadcastReceiver is destroyed too and thus probably unregistered as well.
The point is, if your Application gets destroyed, your Service will be killed as well. Unless you launched it in another process. But then it will have it's own instance of Application; and this is a complex thing you probably do not want to get into details now...
Important: since the Application class is not tied to any UI component, you can do whatever you need directly inside your service. If you need to manipulate the UI, then the pattern described above will work for you.
Please read about new Android's background limitations.
Edit:
Oh yeah right, if you need your Service to call a function declared in your Application class, you can just do
((MyApplication) getApplication()).myFunctionToHandleData(Intent intent);
I didn't really understand your question though, but either of the methods described above should work for you.
I am bit new to android. I would like to know how to communicate with a foreground started service.
So, I got a Foreground service with a notification.
This notification has a (X) button to stop the service.
The service got a Static broadcastreceiver.
public static class NotificationStopButtonHandler extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"Close Clicked",Toast.LENGTH_SHORT).show();
Log.i(LOG_TAG, "In Closed");
// imposible to do context.stopForground(true) or
// to call any other private coded by me
}
}
So my question is :
Is BroadcastReceiver is the best way ?
If it is : How I can communicate with the service to call stopForeground in the broadcastReceiver ?
Thanks in advance for your responses.
Same question like mien... But I would like to know which are the other solution than broadcastReceiver. thx
In your notification you will have a PendingIntent for the X button. I presume you have built that PendingIntent with
PendingIntent.getBroadcast(/* ... */);
What you can do instead is to create a PendingIntent for your service
Intent intent = /* intent for starting your service */;
intent.putExtra("STOP_FOREGROUND", true);
PendingIntent.getService(context, requestCode, intent, flags);
and in the intent you pass to the PendingIntent you would add an extra (STOP_FOREGROUND). When this intent is fired, your service will get called in onStartCommand(). Here you check the intent and if it contains your extra, you know you're expected to call stopForeground.
Instead of broadcasts, you can use PendingIntent with an Intent to the Service and tell the Service to shut down. You assign the PendingIntent to the close button action and/or to the notifications onDelete call when you build the notification.
Assuming that you're starting the Service with the notification, you can put commands in the Intent to tell the service to stop itself. Service#onStartCommand will be called on the service with the new Intent. The service checks for the shutdown call and calls stopSelf() when done.
Basically, the reason this works is because there can only be one Service started. Every subsequent attempt to start the service will send the intent to Service#onStartCommand, but it will not restart the Service. Thus, this is a way you can send commands to the service through means outside of binding. Plus it's way cleaner than using broadcasts.
Is it possible to call the BOOT_COMPLETED broadcastreceiver programmatically? I use it in the normal way but I want to execute it another time during runtime.
You can't send ACTION_BOOT_COMPLETED yourself. According to the docs:
"This is a protected intent that can only be sent by the system."
http://developer.android.com/reference/android/content/Intent.html#ACTION_BOOT_COMPLETED
You can of course send your own intent, and trigger the same code to be called.
You are welcome to call sendBroadcast() to trigger your own BroadcastReceiver.
Usually, it is simpler just to have the common code -- needed both at boot time and at other times -- in some static method or helper class. Then, you do not need to actually call sendBroadcast(), as you can just use the static method or helper class to do the work.
I have a BroadcastReceiver, and the onReceive is called from two different postExecute methods in two different asyncTasks, in two different Activities.
I have a third activity that is running all the time called HomeActivity, and I want to publish some text to the HomeActivity's UI from the onReceive method.
Is it possible? I know that the context parameter is the context of the activity who raised the onReceive, but I want to access the HomeActivity's UI.
Here is the code
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// here I want to publish some text to the HomeActivity
}
}
any ideas? thanks in advance
You want to change text in your running activity based on what you receive in the onReceive of the BroadcastReceiver? Right? One way is that you can use LocalBroadcast. See LocalBroadcast Manager.
For how to implement is, there is a great example on how to use LocalBroadcastManager?.
LocalBroadcast Manager is a helper to register for and send broadcasts of Intents to local objects within your process. The data you are broadcasting won't leave your app, so don't need to worry about leaking private data.`
Your HomeActivity can registers for this local broadcast. From the MyBroadcastReceiver you send a LocalBroadcast from within the onReceive (saying that hey, I received a message. Do you want to do something now activity). Then inside your Activity you can listen to the broadcast. This way if the activity is in the forefront/is active, it will receive the broadcast otherwise it won't. So, whenever you receive that local broadcast, you may change the text etc, if activity is open.