How to use Actions from Notification without starting Activity - android

So I'm working on a simple music player. The music player as it names states, can play a song, pause playback, forward to next song, go back to previous song, and stop playback completely. When you play a song, there will be a notification displayed with the Artist Name, and the Song Name; this notification also has three buttons (Actions): Stop, Pause an Next.
What I'm having issues with is making sure that when either action is clicked, the playback control related to the Action is triggered, and well, I have absolutely no idea on how to do that. I searched the Android Notification: http://developer.android.com/guide/topics/ui/notifiers/notifications.html but It does not clarifies, or dedicates too much info on Notification Actions.
Here is a simple action for instance (which should be associated with the "Next" button click on the notification: Note: All of the code described below is written under the following package: com.deadpixels.light.player and the Activity is called: PlayerActivity
public void nextSong() {
if (position == songs.size() -1) {
Toast.makeText(this, "No more songs on queue", Toast.LENGTH_SHORT).show();
if(mPlayer.isPlaying()) {
return;
}
else {
buttonPlay.setImageResource(R.drawable.button_play);
}
return;
}
else {
position++;
playSong(songs.get(position));
}
}
Here is what I tried to do:
Intent nextIntent = new Intent(KEY_NEXT);
PendingIntent nextPendingIntent = createPendingResult(0, nextIntent, 0);
Where the action for the NextIntent is:
public static final String KEY_NEXT = "com.deadpixels.light.player.PlayerActivity.nextSong";
and then I add that to the Notification via addAction():
mBuilder.addAction(R.drawable.not_action_next, "Next", nextPendingIntent);
Do you guys know what else I could try? Using what I explained above does nothing at all, the notification shows up, and has three action buttons, but clicking on them does nothing for me.
At one point I thought maybe if I added the intent filters with the action names, but then I thought, well, they are all on the same namespace, why should I?

I've solved this problem using broadcasts. For example, to send a broadcast that advances to the next song, you can use the following:
Intent nextIntent = new Intent(KEY_NEXT);
PendingIntent nextPendingIntent = PendingIntent.getBroadcast(this, 0, nextIntent, 0);
Then, register a BroadcastReceiver within your Activity:
IntentFilter filter = new IntentFilter();
filter.addAction(KEY_NEXT);
// Add other actions as needed
receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(KEY_NEXT)) {
nextSong();
} else if (...) {
...
}
...
}
}
registerReceiver(receiver, filter);
If you're using a service to manage background playback, then you'll want to register the BroadcastReceiver in the service instead of the activity. Be sure to store the receiver instance somewhere so that you can unregister it when the activity or service is shut down.

Related

Receive callback on all Android media button events all the time (even when another app is playing audio)

Background Info: I need to detect whenever a user presses the play/pause button found on most headsets (KEYCODE_MEDIA_PLAY_PAUSE).
I have it all mostly working using MediaSessions, but when another app starts playing audio, I stop getting callbacks.
It seems like this is because the app that's playing audio created its own MediaSession and Android sends KeyEvents only to the newest MediaSession. To prevent this I create an OnActiveSessionsChangedListener and create a new MediaSession every time it fires.
This does work, but every time I create a new MediaSession, the listener fires again, so I find myself stuck in an inf loop.
My Question: does anyone know how I can do any of the following??:
Prevent other apps from stealing my media button focus
Detect when I've lost media button focus to another app, so I can create a new MediaSession only then, rather then whenever the active
sessions change
Check if I currently already have media button focus so I needlessly create a new MediaSession
What didn't work:
BroadcastReceiver on
AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION didn't work
because apps have to manually trigger that Broadcast, and many apps,
like NPR One do not
AudioManager.OnAudioFocusChangeListener didn't work because it requires I have
audio focus
BroadcastReceiver with max priority on android.intent.action.MEDIA_BUTTON & calling abortBroadcast(), but when other apps were playing audio, my receiver wasn't triggered. Also, other apps can set max priority as well.
My Code:
mMediaSessionManager.addOnActiveSessionsChangedListener(controllers -> {
boolean updateButtonReceiver = false;
// recreate MediaSession if another app handles media buttons
for (MediaController mediaController : controllers) {
if (!TextUtils.equals(getPackageName(), mediaController.getPackageName())) {
if ((mediaController.getFlags() & (MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)) != 0L) {
updateButtonReceiver = true;
}
}
}
if (updateButtonReceiver) {
// using a handler with a delay of about 2 seconds because this listener fires very often.
mAudioFocusHandler.removeCallbacksAndMessages(null);
mAudioFocusHandler.sendEmptyMessageDelayed(0, AUDIO_FOCUS_DELAY_MS);
}
}, ClickAppNotificationListener.getComponentName(this));
Here is the handler that gets triggered:
private final Handler mAudioFocusHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (mShouldBeEnabled) {
updateButtonReceiverEnabled(true);
}
}
};
And finally here is the method that the Handler triggers:
private void updateButtonReceiverEnabled(boolean shouldBeEnabled) {
// clear old session (not sure if this is necessary)
if (mMediaSession != null) {
mMediaSession.setActive(false);
mMediaSession.setFlags(0);
mMediaSession.setCallback(null);
mMediaSession.release();
mMediaSession = null;
}
mMediaSession = new MediaSessionCompat(this, MEDIA_SESSION_TAG);
mMediaSession.setCallback(mMediaButtonCallback);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
mMediaSession.setActive(true);
mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE)
.setState(PlaybackStateCompat.STATE_CONNECTING, 0, 0f)
.build());
if (shouldBeEnabled != mShouldBeEnabled) {
getPackageManager().setComponentEnabledSetting(mMediaButtonComponent,
shouldBeEnabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
mShouldBeEnabled = shouldBeEnabled;
}
Thanks!
if you just want to capture MediaButton you can register a BroadcastReceiver to get Media Button action all the time .
MediaButtonIntentReceiver class :
public class MediaButtonIntentReceiver extends BroadcastReceiver {
public MediaButtonIntentReceiver() {
super();
}
#Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
return;
}
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
// do something
Toast.makeText(context, "BUTTON PRESSED!", Toast.LENGTH_SHORT).show();
}
abortBroadcast();
}
}
add this to manifest.xml:
<receiver android:name=".MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
and register your BroadcastReceiver like this ( in main activity)
IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
MediaButtonIntentReceiver r = new MediaButtonIntentReceiver();
filter.setPriority(1000);
registerReceiver(r, filter);
also look at :
How to capture key events from bluetooth headset with android
How do I intercept button presses on the headset in Android?
The controllers you get in OnActiveSessionsChangedListener is ordered by priority. You only have to create a new MediaSession if you see that your MediaSessionis not the first one in the list.
Note that you might still run into an infinite loop if there is another app contending the media key events using the same approach.

Get the PendingIntent event when notification is clicked

I'm trying to get the event click when a notification is clicked.
What I have
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String MyText = "Test";
Notification mNotification = new Notification(R.drawable.notification_template_icon_bg, MyText, System.currentTimeMillis() );
String MyNotificationTitle = "Test!";
String MyNotificationText = "Test!";
Intent MyIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
PendingIntent StartIntent = PendingIntent.getActivity(getApplicationContext(),0,MyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
makeToast(StartIntent.getIntentSender().toString());
mNotification.setLatestEventInfo(getApplicationContext(), MyNotificationTitle, MyNotificationText, StartIntent);
notificationManager.notify( NOTIFY_ME_ID, mNotification);
This is working perfect, but the thing that I don't know how to do is get the click on that notification.
What I've tried
I tried to do something on onUserInteraction() that if I'm not wrong seems to get fired when Intent starts a new activity, but didn't work.
Also I've tried on onActivityResult() but I don't know how to get that current Intent.
And the last thing that I've tried is doing something like this
BroadcastReceiver call_method = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action_name = intent.getAction();
if (action_name.equals("MyIntent")) {
//STUFF HERE
}
};
};
registerReceiver(call_method, new IntentFilter("MyIntent"));
Also instead of put MyIntent that is the Intent, I've tried to put the PendingIntent but doesn't work.
By the way on my code appears this when I try to create a Notification
And this when I try to call the setLatestEventInfo()
But I don't know if it may be the cause of the problem or if it could bring problems in the future.
What I'm doing wrong?
EDIT
I've just created a sample what my app does at the moment. It's simple when I press a Button it pop ups a Notification. On my real APP I don't have to click a Button but it's the same. The thing that I want is get the event of the click on the Notification and make things with that event. The thing that I've done is create another Activity where I put the things that I want and then on onCreate() at the end of the things that I want to do I call Finish() method to finish that Activity, but I don't know if it's the best approach. I want another way to do it I don't want to use two Activities...
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btnoti;
private static final int NOTIFY_ME_ID=1337;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnoti = (Button)findViewById(R.id.btnoti);
btnoti.setOnClickListener(this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onClick(View v) {
if (v.getId() == R.id.btnoti){
addNotification();
}
}
private void addNotification() {
//We get a reference to the NotificationManager
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String MyText = "Test";
Notification mNotification = new Notification(R.mipmap.ic_launcher, MyText, System.currentTimeMillis() );
String MyNotificationTitle = "Test!";
String MyNotificationText = "Test!";
Intent MyIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
PendingIntent StartIntent = PendingIntent.getActivity(getApplicationContext(),0,MyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
mNotification.setLatestEventInfo(getApplicationContext(), MyNotificationTitle, MyNotificationText, StartIntent);
notificationManager.notify( NOTIFY_ME_ID, mNotification);
}
Edit 2 (Three fast questions) to go ahead with my code...
I hope you don't mind to solve to me that three fast questions...
Since now I've used Thread.sleep() to do a task for example every 30 seconds with a while(true) but I don't know if it's the best way because I want to let the user choose the time, for example time could be 5 min or 5h... And I don't know what's the best way to take, I've read that theres a method or something called AlarmManager is the correct way to repeat tasks? Is there any source sample to know how to use this Alarm Manager?
I've to know when the user make a "finish()" from the Intent (ACTION_PICK_WIFI_NETWORK) I mean when I'm back to my APP after close that Intent I've have used onResume() but I don't know if it's the correct way to work with, isn't it? (If you don't understand what I'm traying to say it's simple, I want to know the name of the event that know when the user closes the Wifi network picker)
Is this a way to make your APP that still alive once you go to another APP? I mean you can go to another APP and your APP is still working without user interact? Because since now, If I go to another APP my app is like sleep or something and doesn't keep running....
I've read something to call the tasks with a Service and I think it goes well, and it still running even if the APP isn't in the Recent APP...
Thanks, if you can't answer me I can make a post for each question but I think those question could be fast to answer.
Normally when we start an Activity from a push notification, the Intent is recovered in the started Activity's onCreate() method.
In your case, you are starting a system-defined Activity using the WifiManager.ACTION_PICK_WIFI_NETWORK action. We do not have access to the onCreate() of this Activity, so it doesn't seem possible to recover the Intent from it.
Because you have created a PendingIntent using getActivity(), its not possible to intercept the Intent using a BroadcastReceiver. A BroadcastReceiver is triggered by a PendingIntent created using getBroadcast().
Also, it is most certainly wrong to use a PendingIntent in place of an Intent (or vice-versa). These two are not really objects of the same type. As such they are not interchangeable. If you observe their namespace classification, it is android.app.PendingIntent versus android.content.Intent.
I know that this is not a "real" answer to your question, but its all I have for now. I will update this answer if I can think of a solution ... Best.

Change android notification text dynamically

I am trying to make a notification for a music player with controls. I am successfully listening to the button click events and functions are being fired properly. Only problem i am facing is changing the text of notification on these click events.
Here's what i am trying.
This is the receiver successfully receiving the calls and firing each and every line perfectly. But i cant the text changing. I think i have to reset the content view to Notification. If so, how do i do that?
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals("stop")) {
ABCFragment.stopSong();
Log.d("Notification","Stopping");
}else if (action.equals("play")) {
ABCFragment.togglePlayPause();
Log.d("Notification","Toggle Play/Pause");
RemoteViews contentView = new RemoteViews(context.getPackageName(),R.layout.notification_layout);
contentView.setTextViewText(R.id.songName, "SOME NEW SONG");
}else if (action.equals("next")) {
ABCFragment.playNextSong();
Log.d("Notification","Next");
}
}
Solution :
I updated my Notification class constructor to pass an extra arguments and got it working!
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals("stop")) {
ABCFragment.stopSong();
Log.d("Notification","Stopping");
}else if (action.equals("play")) {
ABCFragment.togglePlayPause();
Log.d("Notification","Toggle Play/Pause");
new ABCNotification(context, "SOME NEW SONG");
}else if (action.equals("next")) {
ABCFragment.playNextSong();
Log.d("Notification","Next");
}
}
constructor is handling the new passed arguments.
you can't really change stuff on notification. It's a bit annoying for sure, you just have to replace the current notification with a new one with the new text.
My app there's an upload process and every 3 seconds we're updating the notification to change percentage.
so simply re-build the notification and call notify() on the NotificationManager with the same ID.

How to add listener on a button in a notification (custom layout)

I have a notification layout which is something like this:
My service is ongoing. I want a notification/event sent to my service when stop button is pressed. Click event on notification view is not required.
When the button is pressed, I want my service to stop.
Currently, I'm trying to do this by sending broadcast to an activity (as I did not find a way to directly inform service of the button press).
My current code is:
Intent intent = new Intent(this, MediaEventsReceiver.class);
PendingIntent pending = PendingIntent.getActivity(this, 1, intent, 0);
contentView.setOnClickPendingIntent(R.id.btnStop, pending);
I've also added the reciver in my manifest:
<receiver android:name=".activities.SplashActivity$MediaEventsReceiver" />
Please use code example(s) to explain.
I'm developing a music player app using SINGLE activity. You can add listener to your button like this:
Bind intent
Intent intent = new Intent("ACTION_NAME");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.previousImageView, pendingIntent);
Implement a custom BroadcastReceiver
private class ActionBroadcastReceiver extends BroadcastReceiver
{
public void onReceive(Context context, Intent intent)
{
switch (intent.getAction())
{
case "ACTION_NAME":
{
doSomething();
break;
}
}
}
}
Register your BroadcastReceiver
IntentFilter filter = new IntentFilter();
filter.addAction("ACTION_NAME");
ActionBroadcastReceiver actionBroadcastReceiver = new ActionBroadcastReceiver();
registerReceiver(actionBroadcastReceiver, filter);
SOLVED:
I've used another activity which is called on button click by PendingIntent.
The new activity binds the service and sends it a stop message.
Right after it, it unbinds itself and finishes.
STILL:
I want a solution which does not involve a third activity. There SHOULD be a way where the button click in notification directly informs service and service stops itself. There is NO NEED of an activity for this task.

how to use delete intent to perform some action on clear notification?

I want to reset a variable of my service when user clears my notification: that's all!
Looking around I see that everyone suggest to add a delete intent on my notification, but intent is used to start an activity, a service o whatever while I just need a thing like this:
void onClearPressed(){
aVariable = 0;
}
how to obtain this result?
Notifications are not managed by your app and all things like showing notifications and clearing them are actually happening in another process. You can't make another app directly execute a piece of code just because of security reasons.
The only possibility in your case is to provide a PendingIntent which just wraps around a regular Intent and will be started on behalf of your app when notification is cleared.
You need to use PendingIntent for sending broadcast or starting a service and then doing what you want in the broadcast receiver or in the service. What exactly to use depends on from which application component you are showing notifications.
In case of broadcast receiver you can just create an anonymous inner class for broadcast receiver and register it dynamically before showing notification. It will look something like that:
public class NotificationHelper {
private static final String NOTIFICATION_DELETED_ACTION = "NOTIFICATION_DELETED";
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
aVariable = 0; // Do what you want here
unregisterReceiver(this);
}
};
public void showNotification(Context ctx, String text) {
Intent intent = new Intent(NOTIFICATION_DELETED_ACTION);
PendingIntent pendintIntent = PendingIntent.getBroadcast(ctx, 0, intent, 0);
registerReceiver(receiver, new IntentFilter(NOTIFICATION_DELETED_ACTION));
Notification n = new Notification.Builder(mContext).
setContentText(text).
setDeleteIntent(pendintIntent).
build();
NotificationManager.notify(0, n);
}
}
Andrei is correct.
If you want multiple messages back such as:
you want to know if the message was clicked
you attached an action with an icon that you want to catch
AND you want to know if the message was canceled
you must register each of those response filters:
public void showNotification(Context ctx, String text) ()
{
/… create intents and pending intents same format as Andrie did../
/… you could also set up the style of your message box etc. …/
//need to register each response filter
registerReceiver(receiver, new IntentFilter(CLICK_ACTION));
registerReceiver(receiver, new IntentFilter(USER_RESPONSE_ACTION));
registerReceiver(receiver, new IntentFilter(NOTIFICATION_DELETED_ACTION));
Notification n = new Notification.Builder(mContext)
.setContentText(text)
.setContentIntent(pendingIntent) //Click action
.setDeleteIntent(pendingCancelIntent) //Cancel/Deleted action
.addAction(R.drawable.icon, "Title", pendingActionIntent) //Response action
.build();
NotificationManager.notify(0, n);
}
Then you can catch the different responses with if, else statements (as Andrei did), or with a switch statement.
Note: I make this response primarily because I could not find this anywhere, and I had to figure it out on my own. (perhaps I will remember it better for that :-) Have Fun!

Categories

Resources