I have a music player which shows a media notification while audio is playing. I want the user to be able to dismiss the notification when music is paused.
I have to use startForeground so that the service will continue running AND it will stay attached to the activity. I've tried using a notification channel, once I kill the activity playback stops as well which isn't what I want (the code is still in there, commented out).
When the notification is shown, I call startForeground. setOngoing is set to playbackState == STATE_PLAYING. I call stopForeground when the service is destroyed or when playbackState == null || STOPPED.
Pastebin: https://pastebin.com/FNTSSzjS
Code snippet (because you need to include code with pastebin)
private void addPlayPauseAction(NotificationCompat.Builder builder) {
String label;
int icon;
PendingIntent intent;
if (playbackState.getState() == PlaybackStateCompat.STATE_PLAYING) {
label = service.getString(R.string.play_pause);
icon = android.R.drawable.ic_media_pause;
intent = intentPause;
builder.setOngoing(true);
} else {
label = service.getString(R.string.play_pause);
icon = android.R.drawable.ic_media_play;
intent = intentPlay;
builder.setOngoing(false);
}
builder.addAction(new NotificationCompat.Action(icon, label, intent));
}
relevant methods in PasteBin are startNotification, stopNotification, createNotification, addPlayPauseAction, and setNotificationPlaybackState
Use stopForeground(false) and rebuild notification. You don't have to use setOngoing while using foreground service. For more information refer to this page
and search for Running a service in the foreground.
Related
I have a media control notification on the lockscreen, that is a foreground service while playing media, and is demoted to a background service when the media is paused using stopForeground(false):
I have it set up this way so that the user can swipe-dismiss the notification when the media is paused. For this most part, this works fine - however, if you pause the media and leave it idle for a long period of time, pressing play on the above notification results in this behavior:
This code is called when the media is paused:
if (mediaManager == null) {
mediaManager = MediaManager.getInstance();
}
lockView.setImageViewResource(R.id.lock_bar_play, R.drawable.ic_action_play);
notiView.setImageViewResource(R.id.status_bar_play, R.drawable.ic_action_play);
startForeground(FOREGROUND_SERVICE, notification);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
stopForeground(STOP_FOREGROUND_DETACH);
else
stopForeground(false);
...and this code is called after pressing play:
if (mediaManager == null) {
mediaManager = MediaManager.getInstance();
}
lockView.setImageViewResource(R.id.lock_bar_play, R.drawable.ic_player_pause);
notiView.setImageViewResource(R.id.status_bar_play, R.drawable.ic_player_pause);
startForeground(FOREGROUND_SERVICE, notification);
Log.i(TAG, "Clicked Play");
Why does it behave this way? The notification exists until I attempt to update the icon, at which point it self destructs and doesn't recreate.
Background
I have a small app where I want to add fingerprint authentication. This authentication is only used to unlock the app.
The important things are:
The main point of the app is to run in background as a service.
The user must to be able to unlock the app from anywhere in the system (not just from the activity).
The user must to touch a notification to start the service, and therefore start the fingerprint authentication.
Problem
The FingerprintService must to be started from a foreground Activity. Even if I put the service in foreground with startForeground(), the FingerprintService emits a warning:
01-01 23:22:11.015 1576-1576/system_process W/FingerprintService: Rejecting com.package.myapp ; not in foreground
01-01 23:22:11.015 1576-1576/system_process V/FingerprintService: authenticate(): reject com.package.myapp
This only happens if I touch the notification when I'm outside the app, e.g., in the home screen. If I touch it when I am in the app, the authentication works and I'm able to unlock using my fingerprint (because the Acticity is focused, in foreground).
My current code (only the relevant part)
From the Activity, display the notification that starts the service:
// NofityManager is a factory class that returns a configured Builder
NotificationCompat.Builder builder = NotifyManager.getBuilder(this);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(SERVICE_NOTIFICATION_ID, builder.build());
When the service is started, put it into foreground and start the fingerprint authentication:
#Override
public void onCreate()
{
NotificationCompat.Builder builder = NotifyManager.getBuilder(this)
.setContentTitle(getString(R.string.notification_title2))
.setContentText(getString(R.string.notification_text));
startForeground(FOREGROUND_NOTIFICATION_ID, builder.build());
// this is the class that I use to setup the FingerprintService
// and call authenticate()
fingerprintValidator = new FingerprintValidator(this);
if(fingerprintValidator.check(false))
{
try
{
fingerprintValidator.startService(this);
}
catch(RuntimeException ex)
{
Log.d(TAG, ex.getLocalizedMessage() + "\n" + ex.getStackTrace().toString());
}
}
else
{
// notify the user
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
return START_STICKY;
}
Question
How can I use the FingerprintService from a custom service running in foreground? I know it is possible because I saw another app doing this, so must to be a way.
Have your notification launch an Activity that runs the fingerprint sensor, then starts your Service.
I have a problem with notification on Android (Xamarin).
My scenario is this:
I have a class handling (inheriting GcmServiceBase) the message and creating the Notification object.
In this class I override OnMessage method in this way:
protected override void OnMessage(Context context, Intent intent)
{
if (intent != null || intent.Extras != null)
{
string messageText = intent.Extras.GetString("message");
string messageTitle = intent.Extras.GetString("title");
Intent app_launch_intent = new Intent(context, typeof(Project.WaitForm));
if (App.Instance == null)
{
Console.WriteLine("GCM: Notification received while application not running...");
app_launch_intent.AddFlags(ActivityFlags.ClearTop);
app_launch_intent.AddFlags(ActivityFlags.SingleTop);
}
else if ((App.Instance != null) && (App.Instance.mainActivity.IsInBackground))
{
App.Instance.Logger.Write("GCM: Notification received while application in background...", LogType.Default, LogLevel.Info);
app_launch_intent = new Intent(context, App.Instance.mainActivity.GetType());
}
else
{
App.Instance.Logger.Write("GCM: Notification received while application in foreground...", LogType.Default, LogLevel.Info);
app_launch_intent = new Intent(context, App.Instance.mainActivity.GetType());
app_launch_intent.AddFlags(ActivityFlags.SingleTop);
}
app_launch_intent.PutExtras(intent.Extras);
app_launch_intent.PutExtra("isNotify", true);
PendingIntent pendingIntent = PendingIntent.GetActivity(this, PushService.notificationId, app_launch_intent, PendingIntentFlags.OneShot);
createNotification(context, app_launch_intent, pendingIntent, messageTitle, messageText);
if (App.Instance == null)
{
Console.WriteLine("GCM: Notification object correctly created.");
}
else
{
App.Instance.Logger.Write("GCM: Notification object correctly created.", LogType.Default, LogLevel.Info);
}
}
}
public void createNotification(Context context, Intent result_intent, PendingIntent pendingIntent, string title, string desc)
{
NotificationManager notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
Notification.Builder builder = new Notification.Builder(this)
.SetContentIntent(pendingIntent)
.SetAutoCancel(true)
.SetContentTitle(title)
.SetContentText(desc)
.SetSmallIcon(Resource.Drawable.notify_icon_transparent)
.SetLargeIcon(PushService.IconAgenda)
.SetTicker(title);
Notification notification = builder.Build();
PushService.notificationId += 1;
notificationManager.Notify(PushService.notificationId, notification);
}
as you can see, when message is received I test if application object exists then I create Intent and notification object (App.Instance is a singleton holding informations about the app itself. mainActivity is the current activity shown on screen).
In this way when notification is clicked, last activity on the top of the stack (current on-screen activity if app is in foreground or last on-screen activity before app went in background) will come
up and something in it will take care about notification itself, called by OnNewIntent.
Everything is working fine except for a thing:
Let's say I have 3 Activity called A, B, C.
I start the app and then I receive 2 different notifications when current activity on screen is A.
Both will be displayed in the top bar without problems.
I click on the first of them and it asks me to open another activity (catched in OnNewEvent of current activity and due to notification type), so I say "yes" and I will have a transition to activity C.
Once reached activity C and after done some work, I click on the other notification.
Clicking on it I will be carried to activity A and then notification will be handled.
I know this "problem" is because at notify creation I use the App.Instance.mainActivity.GetType() to create intent that will be use to create PendingIntent and that at that time was activity A for both.
So my question is:
How can I handle second notify click in last activity on screen (so in activity C) instead of activity A?
(I add Xamarin tag just because code above is in C# and not in Java so it may sound strange to a native Android developer)
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.
I have a background service in which I want to show a notification which allows the user to stop it.
In the android SDK docs it says an activity is used to normally launch an Activity. So I am wondering if I need to create an activity to stop the service or can I directly stop the service when user selects the notification,
So how would the intend call back the service to stop it..
Thanks,
So I am wondering if I need to create an activity to stop the service or can I directly stop the service when user selects the notification,
You cannot directly stop the service from a Notification. You can start the service, using an Intent that has an action string or extra or something that the service sees in onStartCommand() and triggers it to call stopSelf().
The question is already old, but since there is still no solution with code, I simply share my code as an example for solving the problem:
You cannot directly stop the service from a Notification. You can
start the service, using an Intent that has an action string or extra
or something that the service sees in onStartCommand() and triggers it
to call stopSelf().
That's the right solution so let's jump in code (this code is all in your ExampleService class):
#RequiresApi(Build.VERSION_CODES.O)
private void startForegroundService() {
// create PendingIntend to open MainActivity (this is when the notification gets clicked) //
Intent tabIntent = new Intent(this, MainActivity.class);
tabIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent tabPendingIntent = PendingIntent.getActivity(this, 0, tabIntent, 0);
// create PendingIntend to open ExampleService (this is when the notification BUTTON gets clicked) //
Intent closeIntent = new Intent(this, ExampleService.class);
closeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
closeIntent.putExtra("destroyCode", 666); // this is the important line //
PendingIntent closePendingIntent = PendingIntent.getService(this, 0, closeIntent, 0);
createNotificationChannel(); // this is only the default code to create notification channel. I just outsourced? it //
Now the Intent has additional data (the "destroy code" -> 666). Notice that we have created 2 pendingIntents: closePendingIntent (stop Service) and tabPendingIntent (start Activity)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
// get extras to know if Intent has destroyCode (666)
Bundle extras = intent.getExtras();
if (extras == null) {
// extras is null which means there is no destroyCode (666)
exampleMethod();
} else {
// Intent has destroyCode (666) -> Intent comes from notification -> stop the service and close notification
stopSelf();
}
return START_STICKY;
}
Now we have the code to check if there is a destroyCode or not. The last step is to create a notification with a button:
// set attributes for notification //
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelID_2");
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.drawable.example)
.setContentTitle(getText(R.string.notificationTitle))
.setContentText(getText(R.string.notificationText))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(tabPendingIntent) //this is when notification is clicked which only opens ExampleActivity
.addAction(R.drawable.example, getString(R.string.notificationButtonText), closePendingIntent) // here is our closePendingIntent with the destroyCode .addAction is "the onClickListener for the notification button"//
.build();
startForeground(2, notification);
In onCreate you start your service
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
startForegroundService();
else
startForeground(1, new Notification());
// Toast Message that service has started
Toast.makeText(this, R.string.serviceStarted, Toast.LENGTH_SHORT).show();
That's it
You can't start an Acitivty from a Service just like that. What you can do is create a callback to an Activity in the Service and let the callback start new activities. But having a notification means you don't have to go through the Service. When the notification is clicked, you can start an activity that's specified in the Intent you supply to the notification. It's really very simple.
Do read the reference docs on notifications for examples.