I am using foregroundService for playing audio in Background and show a notification with actions.
public NotificationHelper(ForegroundService service) {
mFG_Service = service;
mNotificationManager = (NotificationManager) mFG_Service.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void showNotification(ChaptersBO chaptersBO, boolean isPlaying) {
Notification notification = createNotification(chaptersBO, isPlaying);
if (notification != null) {
if (isPlaying) {
mFG_Service.startForeground(NOTIFICATION_ID.FOREGROUND_SERVICE, notification);
} else {
mFG_Service.stopForeground(false);
mNotificationManager.notify(NOTIFICATION_ID.FOREGROUND_SERVICE, notification);
}
}
}
private Notification createNotification(ChaptersBO chapter, boolean isPlaying) {
if (chapter == null)
return null;
NotificationCompat.Builder builder = new NotificationCompat.Builder(mFG_Service);
try {
Bitmap sourceBitmap = BitmapFactory.decodeResource(mFG_Service.getResources(), R.drawable.notification_logo);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(sourceBitmap, 300, 300, false);
if (resizedBitmap != sourceBitmap) {
sourceBitmap.recycle();
}
builder.setContentTitle("Audio")
.setContentText(chapter.getChapterNumber())
.setPriority(Notification.PRIORITY_DEFAULT)
.setSmallIcon(getSmallIcon(isPlaying))
.setLargeIcon(resizedBitmap)
.setShowWhen(false)
.setOngoing(isPlaying)
.setContentIntent(getPendingIntent())
.addAction(R.drawable.ic_notif_prev, "", getAction(ACTION.PREV_ACTION, REQ_CODE_PREVIOUS))
.addAction(getSmallIcon(isPlaying), "", getAction(ACTION.PLAY_PAUSE_ACTION, REQ_CODE_PLAY_OR_PAUSE))
.addAction(R.drawable.ic_notif_next, "", getAction(ACTION.NEXT_ACTION, REQ_CODE_NEXT))
.setDeleteIntent(getStopIntent())
.setStyle(new NotificationCompat.DecoratedCustomViewStyle());
} catch (Exception e) {
e.printStackTrace();
}
return builder.build();
}
I want to update notification small icon and center action icon on play/pause. For the first time it works fine but if I clear the notification and play another audio then these icons (Encircled in below screenshot) are not updating.
To pause audio I am calling this:
mNotificationManager.notify(NOTIFICATION_ID.FOREGROUND_SERVICE, notification);
If I replace it with this
mFG_Service.startForeground(NOTIFICATION_ID.FOREGROUND_SERVICE, notification);
then it works fine but in that case I can't clear notification.
Any suggestions to update these icons on Play/Pause and also make the notification removable when audio is paused will be appreciated.
Related
I wrote service to watch ping devices connected to my wifi in order to turn off heating in apartment on/off.
I was about to buy raspberry pi, but I got idea to use my old android 10 phone running lineage os.
I did write the service. It runs fine, until I turn the screen off. Then it stops pinging the devices and controling the heating api.
Once I turn the screen back on, it starts by itself again.
This is how I start the service:
public void StartService()
{
var intent = new Intent(context, typeof(AutomationService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
context.StartForegroundService(intent);
}
else
{
context.StartService(intent);
}
}
Notification:
public Notification GetNotification()
{
// Building intent
var intent = new Intent(context, typeof(MainActivity));
intent.AddFlags(ActivityFlags.SingleTop);
intent.PutExtra("Title", "Message");
var pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.UpdateCurrent);
var notifBuilder = new NotificationCompat.Builder(context, foregroundChannelId)
.SetContentTitle("Netatmo automation")
.SetContentText("Running")
.SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha)
.SetOngoing(true)
.SetContentIntent(pendingIntent);
// Building channel if API verion is 26 or above
if (global::Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
NotificationChannel notificationChannel = new NotificationChannel(foregroundChannelId, "Title", NotificationImportance.High);
notificationChannel.Importance = NotificationImportance.High;
notificationChannel.EnableLights(true);
notificationChannel.EnableVibration(true);
notificationChannel.SetShowBadge(true);
notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });
var notifManager = context.GetSystemService(Context.NotificationService) as NotificationManager;
if (notifManager != null)
{
notifBuilder.SetChannelId(foregroundChannelId);
notifManager.CreateNotificationChannel(notificationChannel);
}
}
return notifBuilder.Build();
}
And the service itself:
[Service]
public class AutomationService : Service
{
public AutomationService()
{
controlApplication = new ControlApplication();
}
public override IBinder OnBind(Intent intent)
{
return null;
}
public const int ServiceRunningNotifID = 9000;
private readonly ControlApplication controlApplication;
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
Notification notif = DependencyService.Get<INotificationHelper>().GetNotification();
StartForeground(ServiceRunningNotifID, notif);
controlApplication.StartApplication();
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
base.OnDestroy();
}
public override bool StopService(Intent name)
{
return base.StopService(name);
}
}
This part controlApplication.StartApplication(); is just starting while(true) loop in task doing the ping checks and sending http requests by logic.
Can someone help me how to keep the service alive when the screen turns off?
I opted to the phone first, since it will be more power efficient than the raspberry.
I will be glad for any input.
Cheers
EDIT--
This is StartApplication method... It doesn´t run on main thread.
public void StartApplication()
{
this.Log().Debug("Starting application");
_ = Task.Run(async () =>
{
await authorizationService.AuthorizeAsync(username, password).ConfigureAwait(false);
pingWatchService.Start();
});
}
The problem is the while(true) loop is run in the main thread. When the screen is off, the loop will block. So you service will be killed by the system. I have repeated your problem when I add a while(true) into the OnStartCommand method. And you can solve it just create a new thread to do the loop. Such as:
Thread thread = new Thread( controlApplication.StartApplication);
thread.Start();
Use the PowerManager Service:
Add the code to your service.
private PowerManager.WakeLock wakeLock;
PowerManager powerManager = (PowerManager)this.GetSystemService(Context.PowerService);
wakeLock = powerManager.NewWakeLock(WakeLockFlags.Partial,Class.Name);
wakeLock.Acquire();
And add the wakeLock.Release(); to the OnDestory method of the service.
I am using Azure Hubs to send notifications. I am able to receive the notification and it displays on the device when I pull down the notifications window. However, I do not see the notification display at the top of the screen as expected. I even "locked" the screen and it didn't display. I got the notification sound and my logs show I received it.
Screen showing received notification
My FirebaseMessageService:
using System;
using System.Linq;
using WindowsAzure.Messaging;
using Android.App;
using Android.Content;
using Android.Support.V4.App;
using Android.Util;
using Firebase.Messaging;
using IDEQ.AQI.Pages;
namespace IDEQ.AQI.Droid
{
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseService : FirebaseMessagingService
{
const string Tag = "FirebaseMsgService";
public override void OnNewToken(string token)
{
// NOTE: save token instance locally, or log if desired
SendRegistrationToServer(token);
}
private void SendRegistrationToServer(string token)
{
try
{
var hub = new NotificationHub(Constants.NotificationHubName, Constants.ListenConnectionString, this);
// register device with Azure Notification Hub using the token from FCM
var registration = hub.Register(token, Constants.SubscriptionTags);
// subscribe to the SubscriptionTags list with a simple template.
var pnsHandle = registration.PNSHandle;
var templateReg = hub.RegisterTemplate(pnsHandle, "defaultTemplate", Constants.FCMTemplateBody, Constants.SubscriptionTags);
}
catch (Exception e)
{
Log.Error(Constants.DebugTag, $"Error registering device: {e.Message}");
}
}
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
string messageBody;
Log.Info(Tag, "From: " + message.From);
if (message.GetNotification() != null)
{
Log.Info(Tag, "Notification Message Body: " + message.GetNotification().Body);
messageBody = message.GetNotification().Body;
}
// NOTE: test messages sent via the Azure portal will be received here
else
{
messageBody = message.Data.Values.First();
}
// convert the incoming message to a local notification
SendLocalNotification(messageBody);
// send the incoming message directly to the MainPage
SendMessageToMainPage(messageBody);
}
private void SendLocalNotification(string body)
{
try
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
intent.PutExtra("message", body);
var requestCode = new Random().Next();
var pendingIntent = PendingIntent.GetActivity(this, requestCode, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new NotificationCompat.Builder(this, Constants.NotificationChannelId)
.SetContentTitle("IDEQ Alert")
.SetSmallIcon(Resource.Drawable.ic_launcher)
.SetContentText(body)
.SetAutoCancel(true)
.SetShowWhen(false)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManagerCompat.From(this);
notificationManager.Notify(0, notificationBuilder.Build());
}
catch (Exception e)
{
Log.Error(Tag, e.ToString());
}
}
private void SendMessageToMainPage(string body)
{
(Xamarin.Forms.Application.Current.MainPage as MainPage)?.AddMessage(body);
}
}
}
//My main activity where I create the channel:
private void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channel = new NotificationChannel(Constants.NotificationChannelId, Constants.NotificationChannelName, NotificationImportance.Default)
{
Description = string.Empty
};
var notificationManager = (NotificationManager) GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
Notification in your system tray will only be displayed if application is in background or turned off. If your application is running, your OnMessageRecieved method will get hit, however Android will not display notification in the system try. This is how life cycle of push notification works in Android.
The only way u can display notification in system tray when application in in foreground is when you force Local Notification like you did in SendLocalNotification method.
I have a service that calls a BroadcastReceiver. It was working fine but after I changed the back-end system for handling notification ids, the final notification update which should update a notification to tell the user that a process has finished. However, that final update would not pass through unless I place a breakpoint in debugging mode in the method handling those notifications. If a breakpoint is not placed or debug mode is not active, that final notification update would not happen and the notification would display its last state which tells the user that the process has not finished, which is not the case.
Here's the method, and the Log.d()s show that the manager.notify() method was called properly to update the notification to signify that the process has finished.
#Override
public void onReceive(Context context, Intent intent) {
Download data = null;
try {
data = (Download) intent.getSerializableExtra(Commons.ARGS.DATA);
} catch (ClassCastException | NullPointerException e) {
e.printStackTrace();
}
switch (intent.getIntExtra(Commons.ARGS.RESULT, Commons.ARGS.FAILED)) {
case Commons.ARGS.DESTROY:
Log.w(TAG, "Service was destroyed");
if (notifications && manager != null) {
Download[] remaining = (Download[]) intent.getParcelableArrayExtra(Commons.ARGS.DATA);
if (remaining == null) break;
for (Download d : remaining) {
// Download has failed since service was destroyed; was never called and has no relations to this case. manager.notify() is called here to update the notification as "cancelled" btw
}
}
break;
case Commons.ARGS.ERR_LOAD:
Log.w(TAG, "Failed to load queue");
break;
}
if (data == null) return;
if (notifications) {
Intent i = new Intent(context, MainActivity.class);
NotificationCompat.Builder builder = new NotificationCompat.Builder(activity, Commons.Notif.DOWNLOAD_PROGRESS)
.setContentIntent(PendingIntent.getActivity(activity, 0, i, 0))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_notif)
.setContentTitle(data.title)
.setColor(context.getResources().getColor(R.color.Accent))
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setGroup(CHANNEL_ID);
if (manager == null) {
manager = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Downloads in Progress", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("All downloads are displayed here");
channel.enableLights(false);
channel.enableVibration(false);
manager.createNotificationChannel(channel);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID);
}
// Notification builder styling is handled here
manager.notify(0, new NotificationCompat.Builder(activity, Commons.Notif.DOWNLOAD_PROGRESS)
.setChannelId(CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notif)
.setGroup(CHANNEL_ID)
.setColor(activity.getResources().getColor(R.color.Accent))
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setGroupSummary(true)
.setContentText("All downloads have finished.")
.setContentTitle("Done")
.build());
manager.notify(DOWNLOAD_BASE_ID | (data.id & 0x00FF_FFFF), builder.build());
Log.d(TAG, "Notification updated");
}
}
Thanks in advance :P
I do not know what exactly is happening but canceling the notification using manager.cancel() before updating it with the success/final notification solved the problem.
int id = // Notification id
if (success) {
Log.d(TAG, "Success Notification updated");
manager.cancel(id);
} else Log.d(TAG, "Notification updated");
manager.notify(id, builder.build());
I'm trying to create a notification for my app that has an action that I can change its icon, title and intent when I click on it.
I see its possible here at 3:33
https://www.youtube.com/watch?v=tKoQatxG0_8&index=73&list=PLOU2XLYxmsIIwGK7v7jg3gQvIAWJzdat_
But I couldn't find a way to do it.
also, If someone know how to use the pause/play icon on the Right watch in the link, i'll like to know that also.
Thanks from advance.
You can access notification actions from notificationObj.actions
Try below: (Note: below code is not complete, but it will give you an idea on how to change action icon)
Notification status = null;
private NotificationCompat.Builder mBuilder;
private NotificationManager mManager;
private final int STATUS_ID = 1;
private String CHANNEL_ID = "channel_name";
private void setUpNotification() {
mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
int playOrPause;
if(isPlaying()) {
playOrPause = R.drawable.ic_pause;
} else {
playOrPause = R.drawable.ic_play;
}
if(mBuilder == null) {
//Setup Builder
mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
mBuilder.setOngoing(true)
.addAction(playOrPause, "play", pendingIntent1) // Index 0
.addAction(R.drawable.ic_2, "text2", pendingIntent2) // Index 1
.addAction(R.drawable.ic_3, "text3", pendingIntent3);// Index 2
status = mBuilder.build();
} else {
//Update builder as per your needs
mBuilder.setContentTitle(title);
status = mBuilder.build();
status.actions[0] = new Notification.Action(playOrPause, "play", pendingIntent1);
}
startForeground(STATUS_ID, status);
}
I was able to solve the same problem by accessing builder.mActions. It's an ArrayList of all the actions you've added. I modify this without recreating the builder and calling build seems to update this.
This doesn't seem to be documented in the SDK but I'm ok with that for now.
Will let you know if I come up with something else
By following #Sammer J suggestion I came up with the following solution.
try {
// mBuilder is a Notification.Builder object.
Field field = mBuilder.getClass().getDeclaredField("mActions");
field.setAccessible(true);
ArrayList<Notification.Action> mActions = (ArrayList<Notification.Action>) field.get(mBuilder);
if (mActions != null) {
mActions.set(0, new Notification.Action(R.drawable.ic_action_resume, getString(R.string.button_resume), pendingIntent));
field.set(mBuilder, mActions);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
mNotificationManager.notify(ONGOING_NOTIFICATION_ID, mBuilder.build());
here is my code :
public class MainActivity extends Activity {
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mBuilder;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
}
public void click(View view) {
noti();
}
private void noti() {
mBuilder.setContentTitle("Picture Download")
.setContentText("Download in progress")
.setSmallIcon(R.drawable.ic_launcher);
// Start a lengthy operation in a background thread
new Thread(
new Runnable() {
#Override
public void run() {
int incr;
// Do the "lengthy" operation 20 times
for (incr = 0; incr <= 100; incr+=5) {
// Sets the progress indicator to a max value, the
// current completion percentage, and "determinate"
// state
mBuilder.setProgress(100, incr, false);
// Displays the progress bar for the first time.
mNotifyManager.notify(0, mBuilder.build());
// Sleeps the thread, simulating an operation
// that takes time
try {
// Sleep for 5 seconds
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// When the loop is finished, updates the notification
mBuilder.setContentText("Download complete")
// Removes the progress bar
.setProgress(0,0,false);
mNotifyManager.notify(0, mBuilder.build());
}
}
// Starts the thread by calling the run() method in its Runnable
).start();
}
}
it works fine on android 4.2.2 device , but when I tried to run it on an android 2.2 device , the notification didn't show up,and I got the following logs:
RuntimeException in notify -
java.lang.Throwable: stack dump
at android.app.NotificationManager.notify(NotificationManager.java:118)
at android.app.NotificationManager.notify(NotificationManager.java:92)
at com.gyh.notitest.MainActivity$1.run(MainActivity.java:49)
at java.lang.Thread.run(Thread.java:1019)
can anyone help me ? how can I display a progress bar in a Notification on android 2.2 devices without custom layout?
thanks!
Never mind..
I change my code to:
public class MainActivity extends Activity {
private NotificationManager mNotifyManager;
private NotificationCompat.Builder mBuilder;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent resultIntent = new Intent(this, MainActivity.class);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(
this,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(
getApplicationContext()).setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setContentIntent(resultPendingIntent);
}
public void click(View view) {
noti();
}
private void noti() {
// Start a lengthy operation in a background thread
new Thread(
new Runnable() {
#Override
public void run() {
int incr;
// Do the "lengthy" operation 20 times
for (incr = 0; incr <= 100; incr+=5) {
// Sets the progress indicator to a max value, the
// current completion percentage, and "determinate"
// state
mBuilder.setProgress(100, incr, false);
// Displays the progress bar for the first time.
mNotifyManager.notify(0, mBuilder.build());
// Sleeps the thread, simulating an operation
// that takes time
try {
// Sleep for 5 seconds
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// When the loop is finished, updates the notification
mBuilder.setContentText("Download complete")
// Removes the progress bar
.setProgress(0,0,false);
mNotifyManager.notify(0, mBuilder.build());
}
}
// Starts the thread by calling the run() method in its Runnable
).start();
}
}
Then there came the Notification , But no progress T_T There is nothing I can do about it .. the only way I can think of right now is a custom layout with a progress bar...
Have you tried import the v4.jar library to your project? It supports features of new api version running on low api ( Android 2.2 for instance).
Cheers,
I re-wrote your code to enable multiple Notification Progress. Also updated to work on android Oreo+
class MainActivity : AppCompatActivity(){
private var mNotifyManager: NotificationManagerCompat? = null
private var mBuilder: NotificationCompat.Builder? = null
private var notificationId = 0
val CHANNEL_ID ="download_progress_notification"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mNotifyManager = NotificationManagerCompat.from(this)
mBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
}
private fun noti(notficationId : Int) {
mBuilder?.setContentTitle("Picture Download")
?.setContentText("Download in progress")
?.setSmallIcon(R.mipmap.ic_launcher)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // you must create a notification channel for API 26 and Above
val name = "my channel2"
val description = "channel description2"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description)
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
// Start a lengthy operation in a background thread
Thread(object : Runnable{
override fun run() {
var incr: Int
// Do the "lengthy" operation 20 times
incr = 0
while (incr <= 100) {
// Sets the progress indicator to a max value, the
// current completion percentage, and "determinate"
// state
mBuilder?.setProgress(100, incr, false)
// Displays the progress bar for the first time.
mNotifyManager?.notify(notficationId, mBuilder!!.build())
// Sleeps the thread, simulating an operation
// that takes time
try {
// Sleep for 5 seconds
Thread.sleep((1 * 1000).toLong())
} catch (e: InterruptedException) {
e.printStackTrace()
}
incr += 5
}
// When the loop is finished, updates the notification
mBuilder?.setContentText("Download complete")
// Removes the progress bar
?.setProgress(0, 0, false)
mNotifyManager?.notify(notficationId, mBuilder!!.build())
}
}).start()
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_option -> {
noti(notificationId++)
return true
}
}
return super.onOptionsItemSelected(item)
}
}