I have an app that has two services.
One is for showing UI for floating (overlay) on other apps using WindowManager. The other is for Location Tracking using GooglePlayAPI. My app always runs these services.
I want these services not to be killed by the OS. So I call Service.startForeground(). However there are two notifications in the notification drawer.
Is there a way to use a single notification for both services?
Yes, it is possible.
If we take a look at Service.startForeground() signature, it accept both notification id & the notification itself (see documentation). Hence, if we want to have an only single notification for more than one foreground services, these services must share the same notification & notification id.
We can use the singleton pattern to get the same notification & notification id. Here is the example implementation:
NotificationCreator.java
public class NotificationCreator {
private static final int NOTIFICATION_ID = 1094;
private static final String CHANNEL_ID = "Foreground Service Channel";
private static Notification notification;
public static Notification getNotification(Context context) {
if(notification == null) {
notification = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Try Foreground Service")
.setContentText("Yuhu..., I'm trying foreground service")
.setSmallIcon(R.mipmap.ic_launcher)
.build();
}
return notification;
}
public static int getNotificationId() {
return NOTIFICATION_ID;
}
}
Thus, we can use this class in our foreground services. For example, we have MyFirstService.java & MySecondService.java:
MyFirstService.java
public class MyFirstService extends Service {
#Override
public void onCreate() {
super.onCreate();
startForeground(NotificationCreator.getNotificationId(),
NotificationCreator.getNotification(this));
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
MySecondService.java
public class MySecondService extends Service {
#Override
public void onCreate() {
super.onCreate();
startForeground(NotificationCreator.getNotificationId(),
NotificationCreator.getNotification(this));
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
Just try to run these services. Voila! You have a single notification for multiple foreground services ;)!
Related
I have an IntentService and inside this instant service, in onCreate, I call the method startforeground(). I see then the notification when the intentService is created. However, when the IntentService is destroyed (going to onDestroy), I can see the notification for few seconds after that the service is destroyed. Why is that?
This is the code of the IntentService:
public class USFIntentService extends IntentService {
private static final String TAG = "USFIntentService";
private static final int USF_NOTIFICATION_ID = 262276;
private static final String USF_NOTIFICATION_CHANNEL_ID = "USF_NOTIFICATION_CHANNEL";
public USFIntentService() {
super("USFIntentService");
}
#Override
public void onCreate() {
super.onCreate();
Log.i(TAG,"in onCreate");
startUsfForegroundService();
}
#Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"in onDestroy");
}
private void startUsfForegroundService() {
// Define notification channel
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel =
new NotificationChannel(USF_NOTIFICATION_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
// Build notification to be used for the foreground service.
Notification notification =
new Notification.Builder(this, USF_NOTIFICATION_CHANNEL_ID)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.usf_notification_icon)
.build();
// Set the service as a foreground service.
startForeground(USF_NOTIFICATION_ID, notification);
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "onHandleIntent");
if (intent != null) {
doStuff();
}
Log.i(TAG,"End of onHandleIntent");
}
}
I call this service like this:
Intent startServiceIntent = new Intent(intent);
startServiceIntent.setComponent(new ComponentName(context, USFIntentService.class));
context.startForegroundService(startServiceIntent);
Try to call Service#stopForeground after your job is done to remove it
You can call stopForeground(true) when you finish doing the stuff. So that your service gets immediately removed from foreground state and the parameter true ensures that the notification will be removed.
If STOP_FOREGROUND_REMOVE is supplied, the service's associated notification will be cancelled immediately.
If STOP_FOREGROUND_DETACH is supplied, the service's association with the notification will be severed. If the notification had not yet been shown, due to foreground-service notification deferral policy, it is immediately posted when stopForeground(STOP_FOREGROUND_DETACH) is called. In all cases, the notification remains shown even after this service is stopped fully and destroyed.
stopForeground(STOP_FOREGROUND_REMOVE) // remove with notification
stopForeground(STOP_FOREGROUND_DETACH) // remove only intent and not notification
I'm trying to build up a service which requests the device location every minute.
I need this to work in the background even when the application is closed. So far I managed to make it work on devices which have a pre-Oreo android OS but now I'm testing the service on android Oreo device and is not working when I close or put the application in background.
In my research I found that for Oreo devices, a Foreground Service with an ongoing notification should be used to achieve this so to start with I've implemented a simple Foreground Service like the below which shows an ongoing notification while the app is started and is removed when the app is stopped.
public class MyForegroundService extends Service {
private static String TAG = MyForegroundService.class.getSimpleName();
private static final String CHANNEL_ID = "channel_01";
private static final int NOTIFICATION_ID = 12345678;
private NotificationManager mNotificationManager;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
public MyForegroundService() {
super();
}
#Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
// Create the channel for the notification
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
// Set the Notification Channel for the Notification Manager.
mNotificationManager.createNotificationChannel(mChannel);
}
startForeground(NOTIFICATION_ID, getNotification());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
stopForeground(true);
}
private Notification getNotification() {
// Get the application name from the Settings
String appName = PrefApp.getSettings(getApplicationContext()).getAppConfigs().getAppName();
String applicationKey = PrefApp.getSettings(getApplicationContext()).getAppConfigs().getAppKey();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(appName)
.setContentText("Services are running")
.setOngoing(true)
.setPriority(Notification.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_notification)
.setWhen(System.currentTimeMillis());
// Set the Channel ID for Android O.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID); // Channel ID
}
return builder.build();
}
}
I am starting and stopping the above service using the below functions.
public void startMyForegroundService() {
Log.d(TAG, "Start Foreground Service");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(getApplicationContext(), MyForegroundService.class));
} else {
startService(new Intent(getApplicationContext(), MyForegroundService.class));
}
}
public void stopMyForegroundService() {
Log.d(TAG, "Stop Foreground Service");
stopService(new Intent(getApplicationContext(), MyForegroundService.class));
}
I'm testing the above service and for some reason the service gets killed after about 30 mins from when I start it. Can anyone tell me if I'm doing something wrong or possibly guide me for a solution which can work for me?
Note: I've followed this tutorial and tested their application as well and that it still not working. The service is being killed after some time.
Basically my goal is to implement a service which can run in the background (even when the application is closed) and get location updates every minutes.
You can use firebase job dispatcher for background service.
Code:
Add this dependencies:
implementation 'com.firebase:firebase-jobdispatcher:0.8.5'
public class MyJobService extends JobService
{
private static final String TAG = MyJobService.class.getSimpleName();
#Override
public boolean onStartJob(JobParameters job)
{
Log.e(TAG, "onStartJob: my job service class is called.");
// enter the task you want to perform.
return false;
}
#Override
public boolean onStopJob(JobParameters job)
{
return false;
}
}
Create a job in the activity, you do it the way you used to do for background services.
/**
* 2018 September 27 - Thursday - 06:36 PM
* create job method
*
* this method will create job
**/
private static Job createJob(FirebaseJobDispatcher dispatcher)
{
return dispatcher.newJobBuilder()
//persist the task across boots
.setLifetime(Lifetime.FOREVER)
//.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
//call this service when the criteria are met.
.setService(MyJobService.class)
//unique id of the task
.setTag("TAGOFTHEJOB")
//don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// We are mentioning that the job is periodic.
.setRecurring(true)
// Run every 30 min from now. You can modify it to your use.
.setTrigger(Trigger.executionWindow(1800, 1800))
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
//.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
//Run this job only when the network is available.
.setConstraints(Constraint.ON_ANY_NETWORK)
.build();
}
/**
* 2018 September 27 - Thursday - 06:42 PM
* cancel job method
*
* this method will cancel the job USE THIS WHEN YOU DON'T WANT TO USE THE SERVICE ANYMORE.
**/
private void cancelJob(Context context)
{
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
//Cancel all the jobs for this package
dispatcher.cancelAll();
// Cancel the job for this tag
dispatcher.cancel("TAGOFTHEJOB");
}
I am getting this error shown in the Android Dashboard crash logs:
Context.startForegroundService() did not then call Service.startForeground() (no location available)
I'm aware of the Background Limitations introduced in Oreo and have read through this post.
However, I'm still getting this error thrown for a small percentage of my users who are running Android Wear 8.0. What makes it confusing is it's not all users running 8.0.
According to the documentation, if you call Context.startForgroundService() you must show a notification by calling startForeground() in the service within 5 seconds (I'm assuming MediaBrowserCompat is calling Context.startForgroundService()).
However, I'm not sure if I need to do that if I'm using a MediaBrowserServiceCompat. I do show a foreground notification when the user hits play to start audio playback.
public class MediaActivity {
private MediaBrowserCompat mMediaBrowserCompat;
#Override
public void onCreate() {
super.onCreate(savedInstanceState);
mMediaBrowserCompat = new MediaBrowserCompat(
this,
new ComponentName(this, MediaPlayerService.class),
mMediaBrowserCompatConnectionCallback,
getIntent().getExtras()
);
mMediaBrowserCompat.connect();
}
}
private MediaBrowserCompat.ConnectionCallback mMediaBrowserCompatConnectionCallback = new MediaBrowserCompat.ConnectionCallback() {
#Override
public void onConnected() {
super.onConnected();
final MediaControllerCompat mcc = new MediaControllerCompat(MediaActivity.this, mMediaBrowserCompat.getSessionToken());
mcc.registerCallback(mMediaControllerCompatCallback);
MediaControllerCompat.setMediaController(mActivity, mcc);
}
};
public class MediaPlayerService extends MediaBrowserServiceCompat {
#Override
public void onCreate() {
super.onCreate();
//Should I add a startForeground notification here
}
}
private MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() {
#Override
public void onPlay() {
super.onPlay();
final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelID)
...
startForeground(101, builder.build());
}
#Override
public void onPause() {
super.onPause();
}
};
You have to use the startForegroundService before calling startforeground to attach the foreground notification if you are running audio mediaCompact service , this will not destroy by the system due to background limitation.
I am trying to put a notification in the status bar when a service starts and keep it there until I stop the service but is disappears after a few seconds(about 10). Any suggestions as to what I am missing? This worked before I tried to re write using notification.builder for compatibility with api 15. The log entry shows onDestroy is not called until I stop the service so it is still running.
public class MyService extends Service {
private NotificationManager mNM;
private int NOTIFICATION = R.string.service_started;
public void onCreate() {
super.onCreate();
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
showNotification();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("MyService", "Service Started");
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
mNM.cancel(NOTIFICATION);
Log.e("MyService", "Service Ended");
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
private void showNotification() {
Notification.Builder builder = new Notification.Builder(getApplicationContext());
builder.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(R.drawable.myicon)
.setTicker(getText(R.string.service_label))
.setWhen(System.currentTimeMillis())
.setContentTitle(getText(R.string.service_started))
.setContentText(getText(R.string.service_label));
Notification notification = builder.getNotification();
mNM.notify(NOTIFICATION, notification);
}
I had the same problem with an ongoing notification disappearing in ICS on a new phone. The app and notification had worked perfectly in every version of Android I had tested it on previously, and it even works on an ICS emulator. Needless to say this has been driving me crazy for a couple months now, but I finally found the answer.
http://code.google.com/p/android/issues/detail?id=21635
I am using a BroadcastReceiver to monitor incoming calls on the handset and I programmatically enable the receiver when a button is toggled in addition to setting the notification. So I wrote a small test app with the same BroadcastReceiver hooked up and was able to reproduced the problem. I commented out the setComponentEnabledSetting call and the notification no longer disappears.
Im creating an application where I can add appointments to a list.
If an appointment is nearby, I want my app to show a notification in the status bar the day of the appointment at a certain hour.
I used the code from http://developer.android.com/guide/topics/ui/notifiers/notifications.html
to create my notification.
However, the "when" parameter in the script is somewhat confusing because a statusbar notification is always triggered when called.
Notification notification = new Notification(icon, tickerText, when);
What is the best way to schedule such notification?
It seems there is no easy way and I have to create a service that starts a listener Activity with a thread to loop my appointmentdates and show a notification when a date fits the current date?
However, the "when" parameter in the script is somewhat confusing
because a statusbar notification is always triggered when called.
Notification notification = new Notification(icon, tickerText, when);
Exactly - notification is triggered when called. If you set when variable to System.currentTimeMilis() as in the example, it means - show the notification now.
As what triggers your notifications, that is up to you to handle. An Activity doesn't seem like a good choice, but a Service does. Initialize your service on application start (and don't forget to stop it on application exit), and let it do the "listening" and triggering of notifications. It might look as:
public class NotifyService extends Service {
private NotificationManager mNM;
#Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
//do some work, listen for change
if (triggerSatisfied) showNotification();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancelAll();
}
#Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
private void showNotification() {
//code for notification goes here
}
public class LocalBinder extends Binder {
NotifyService getService() {
return NotifyService.this;
}
}