There are a number of similar questions on SO, but they are all pre-O, where IntentService works just fine. I tried to do it as a JobIntentService, but the delay is unacceptable. The network exchange for my widget is supposed to be really quick, or not at all, the socketTimeout is set to 100ms, so several seconds of delay is frustrating.
I want to try several solutions here. First, to create a foregroundService from the context. As far as I understood, I have 5 seconds before the service is killed, so if my exchange takes just a fraction of that, I should be good. Is this assumption correct? Is this a good use for a foregroundService?
Next, what if I just do it by starting manually a new thread in onReceive of my AppWidgetProvider? As I said, the network exchange should take less than the quarter of a second. What can possibly happen to the context during that time?
What is the best way to make a quick network request from an appWidget, that should happen immediately after it sends the broadcast?
There are several things to point out in answering this. We have appWidgets that use a service to handle transactions because jobService is not an option for us. Sharing a few things we have done which will hopefully help you.
On your first question:
The service is called from the appWidget provider and does all the work, and it must be called as a foreground service on Oreo+, if that foreground service is not started within 5 seconds of that call you will get an exception: "Context.startForegroundService did not then call Service.startForeground". There is an unresolved issue in the Google tracker that doesn't seem to have been resolved yet about this issue, so you should make sure you read this as well. Some have suggested putting the following code in both onCreate() and onStart() of the service. The issue is https://issuetracker.google.com/issues/76112072.
I would suggest doing all the work you need in the Service after calling it from the provider. (Also remember to use a notification channel for Oreo+) For example:
In appWidget provider onUpdate():
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(serviceIntent));
} else {
context.startService(new Intent(serviceIntent));
}
Then in your service class- put this in onStartCommand() and onCreate() per the issue link I've posted above.
savedIntent = intent;
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//Google Issue tracker https://issuetracker.google.com/issues/76112072 as of 9/26/2018. StartForeground notification must be in both onCreate and onStartCommand to minimize
//crashes in event service was already started and not yet stopped, in which case onCreate is not called again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(PRIMARY_NOTIF_CHANNEL, "YOUR NOTIFICATION CHANNEL", NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(null, null);
channel.enableVibration(false);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, PRIMARY_NOTIF_CHANNEL)
.setSmallIcon(R.drawable.your_drawable)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(getText(R.string.app_name))
.setStyle(new NotificationCompat.InboxStyle()
.addLine("Needs to sync periodically. Long press to hide.")
.addLine(""))
.build();
startForeground(PRIMARY_FOREGROUND_NOTIF_SERVICE_ID, notification);
}
On your second question:
AppWidget Provider's onReceive() method is triggered by broadcasts with intents, which can be used to do different tasks, partially update the appWidget, or restart the service. So based on the information you've provided, you should be able do your network call in the onReceive area of the provider, but you may need to specify a custom intent for it to trigger onReceive(), and make sure you update all instances of the appWidget as well to update the remote views. As an example, we have several custom intents which trigger the onReceive() and partially update our appWidget:
In appWidget provider:
// sample custom intent name
public static final String ACTION_CLICK_ADVANCE_STATUS = "widget_action_click_advance_status";
...
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION_CLICK_ADVANCE_STATUS.equals(intent.getAction())) {
// update all instances of appWidgets
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetJobProvider.class));
// Do your work here
}
Related
I'm facing a bit of a dilemma with the new foreground service requirements introduced in Android O.
I have a foreground service that creates the foreground notification during onCreate() like this:
public class FooService extends Service {
#Override
public void onCreate() {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.createNotificationChannel(new NotificationChannel("channel", "channel", IMPORTANCE_LOW));
startForeground(42, new NotificationCompat.Builder(this, "channel")
.setContentTitle("Foo")
.setContentText("Bar")
.setOngoing(true)
.setSmallIcon(android.R.drawable.ic_popup_sync)
.build());
}
}
However, an issue arises: What if I need to dynamically pass data to the notification, eg. set a formatted title? Something like this:
startForeground(42, new NotificationCompat.Builder(this, "channel")
.setContentTitle(getString(R.string.formatted_title, intent.getStringExtra("Foobar")))
...
There's no idiomatic way to pass the argument to the notification since there's no intent passed to onCreate(). There is the method onStartCommand() that gets an intent as an argument, but there's a catch: startForeground() must be called within five seconds of starting the service, but onStartCommand() is not guaranteed to be called before the five-second limit is hit. I've experimented with this for a while and it happens about 10% of the time. The only way seems to be to call startForeground() during onCreate().
Now, what would be the correct way to approach the dynamic notification content issue?
I want to stream an mp3 file that has a length of about 1 hour. I want to play the file in background. I saw many tutorial videos. Some of them used service and some used asyntask. I dont know what can i choose between these too. Which is the better one?
You'll definitely want to use a service, and not an AsyncTask. The main reason for this being that, if you want the music to run even when the app has been suspended/put into the background, like when the user moves to another app, only a service will do this. AsyncTasks will not run in the background in that way.
To include some background information about background services, they use events from app contexts such as activities and foreground services to notify them of when to do work. This work is handled via the service's onStartCommand() function. More can be read about services in the Android docs https://developer.android.com/guide/components/services
That being said, a service will allow running in the background, but it can still be preempted if the OS needs to complete another task. Therefore, for the music to play reliably, and restart shortly after the OS has preempted the service for any reason, you will need to specify START_STICKY as the return value from the service's onStartCommand() function. But, like with everything Android, prefer the compatibility version, START_STICKY_COMPATIBILITY, to the not compatible version. START_STICKY/START_STICKY_COMPATIBILITY is appropriate to return in the case of the PLAY command. I.e. if the event the service is receiving is PLAY.
Returning START_STICKY or START_STICKY_COMPATIBILITY from the onStartCommand() in every case will wind you up with a service that never dies, thus consuming processing power and battery life from the phone running it. This could cause processor consumption and a drain on battery. That is why it is important to return START_NOT_STICKY from the onStartCommand if the user is attempting to pause. I.e. if the event the service is receiving is PAUSE.
Here is a stripped down version of what you might want your onStartCommand of your service to look like:
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_PLAY)) {
...
return START_STICKY_COMPATIBILITY;
} else { // i.e. action is ACTION_PAUSE
...
return START_NOT_STICKY;
}
}
Edit: To caveot this and the remainder of this answer - In an attempt to simplify the post, I excluded considerations for waiting for the mediaplayer to prepare. As a note, the service will likely also need to handle waiting for the mediaplayer to prepare with either a separate event, or from within the handling of the PLAY event. The could possibly also be handled from within the activity before starting the service but this may be more or less complicated. Explaining the rest of the issues/considerations in this answer is much easier without talking about this aspect of the problem, although it will have to be considered to make a functional music player app.
Provisions for when the device is locked are also required so that some hardware peripherals don't shut off. Consider adding the following in response to the PLAY event in the service's onStartCommand to account for this:
// Setup Wake Mode Wake Lock so the music can play while device screen is locked
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.FULL_WAKE_LOCK);
// Setup wifi lock so wifi can stay on while device is locked and trying to save power
wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
Another issue is that your user will ideally be able to kill the service if it is running. If they kill the app, it will not kill the service, as intended, and the music will keep playing. So the user should be able to control the service via a notification with controls to pause and play the music. This can be done using a foreground service. If you want to add the foreground service layer, you can add a call to startForeground() in the onStartCommand() of the service in response to the broadcast event for PLAY. Here is the stripped down onStartCommand() with the foreground logic added:
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_PLAY)) {
...
Notification notification = setupNotification(); // where setupNotification is your own function
startForeground(1, notification);
return START_STICKY_COMPATIBILITY;
} else { // i.e. action is ACTION_PAUSE
...
stopForeground(false);
// NotificationManagerCompat needed here for swipeable notification b/c service will be killed
Notification notification = setupNotification();
NotificationManagerCompat nmc = NotificationManagerCompat.from(this);
nmc.notify(1, notification);
return START_NOT_STICKY;
}
}
The startForeground() functions takes an id and a Notification object as its params. The notification can be created with the NotificationCompat.Builder with code that looks something like this (noting that some variables here will need to be subbed out for your respective application):
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher_round_large);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("Music is now playing") // change to paused on paused
.setTicker("Music Playing")
.setSmallIcon(R.drawable.status_bar_icon_xhdpi_48px)
.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
.setContentIntent(pendingTapIntent)
.setDeleteIntent(pendingSwipeIntent)
.addAction(iconId, buttonText, pendingButtonIntent)
.setWhen(System.currentTimeMillis());
Note the pending intents in the code above. These are created with the PendingIntent class.
I.e. create a pending intent for a play button on the notification like so (where "this" is the background service, assuming you are creating this intent from within the background service)
Intent playIntent = new Intent(this, MyService.class);
playIntent.setAction(ACTION_PLAY);
PendingIntent pendingPlayIntent = PendingIntent.getService(this, 0, playIntent, 0);
Likewise, create a pending intent for when the user taps on the notification so that it opens the app with the following code (again, where "this" is the background service, assuming you are creating this intent from within the background service):
Intent notificationIntent = new Intent(this, StreamActivity.class);
notificationIntent.setAction("ACTION_MAIN");
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Furthermore, create the pending intent for swipe action on the notification to kill the background service like this (again, where "this" is the background service, assuming you are creating this intent from within the background service):
Intent swipeIntent = new Intent(this, MyService.class);
swipeIntent.setAction(ACTION_END_SERVICE);
PendingIntent pendingSwipeIntent = PendingIntent.getService(this, 0, swipeIntent, 0);
Hopefully this covers enough to get you going, but I would recommend starting this process without the foreground activity layer of complexity. Then add it once you see the music play from within the app is working.
If you want the music to play even after user exits the app, you need to use a foreground service for this. All music players use this approach.
If you want the music to end when user exists a perticular activity, only in that case would consider anything other than the service.
Async Task should not be used for this purpose as it is ideal for small background tasks. Playing music is not in this category.
I'm developing an app which needs to run some code (Networking) whenever an SMS is received.
In API 25 and lower it's fine, I register an implicit receiver in Manifest file and start my service in the specified class which extended BroadcastReceiver. In API 26 however you cannot register android.provider.Telephony.SMS_RECEIVED in a receiver since it won't work.
From Android documentation:
Note: If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for implicit broadcasts (broadcasts that do not target your app specifically), except for a few implicit broadcasts that are exempted from that restriction. In most cases, you can use scheduled jobs instead.
I've read several articles like this one on medium. There are solutions like JobScheduler or Explicit Receiver, however the first one is used for changes in network state and I couldn't find a way to trigger the job on SMS_RECEIVED event and the second one is valid until your activity is up and running.
Because of the nature of my application I need to listen for incoming SMS whether the app is running or not. How to do that in API 26+?
Edit
Maybe the code in JobInfoBuilder doc on android website could help. It monitors the changes in the photos on a device and start the job on change. However I cannot find a proper Uri to do the same with the SMS (not even sure if it's possible to monitor SMS via ContentObserver)
Since there are lots of ways to do the job in android O, I post this answer and mention my approach to solve the problem. Obviously by problem I mean the general problem not the SMS_RECEIVED receiver itself.
I start a foreground service and in there I register a dynamic or explicit receiver to listen to the incoming calls (for instance):
In MainActivity.java:
String action = "START"
final Intent intent = new Intent(this, CallMonitorService.class);
intent.setAction(action);
startService(intent);
In CallMonitorService.javas onCreate() method where I have BroadcastReceiver callExplicitReceiver as a field:
final IntentFilter intentFilter = new IntentFilter();
intentFilter.setPriority(2147483647);
intentFilter.addAction("android.intent.action.PHONE_STATE");
this.callExplicitReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
// do the stuff here
}
}
};
registerReceiver(callExplicitReceiver, intentFilter);
and then in onStartCommand():
if (intent.getAction().equals("START")) {
Intent callServiceNotificationIntent = new Intent(this, MainActivity.class);
callServiceNotificationIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent
.getActivity(this, CALL_SERVICE_REQUEST_CODE,
callServiceNotificationIntent, CALL_SERVICE_FLAG);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle(CALL_NOTIFICATION_CONTENT_TITLE)
.setTicker(CALL_NOTIFICATION_TICKER)
.setContentText(CALL_NOTIFICATION_CONTENT_TEXT)
.setSmallIcon(R.drawable.ic_info_outline_black_24dp)
.setContentIntent(pendingIntent)
.setOngoing(true)
.build();
startForeground(CALL_NOTIFICATION_ID, notification);
}
and finally:
#Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(callExplicitReceiver);
}
I think of this as a good approach since the user is notified of the running service because of the undismissable notification and that's what android Oreo wants, however through a button in the app user could stop the service and the monitoring receiver as a direct result of destroying service (I cleared that part of code).
I think for now you are safe as SMS_RECEIVED_ACTION is present in the current exempted implicit broadcast list. Also, upon receiving the system broadcast you can either start a foreground service or schedule a job (to perform network operation in your case). Moreover, I am also using the same action and upon testing it seems to work okay.
I have a question regarding Android widgets and especially Android services. Here is what I want to do: On the phone the user should be able to add my widget to his homescreen. The widget gets its data from network.
After reading some tutrials about this, I found out that the best approach would be to create a service that gets the data from network (TCP sockets) and then updates the widget constantly. That is done with ScheduledExecutorService which executes a Runnable in a certain interval.
The Runnable will connect to a server, get the data and should update the widget).
My problem is now, that the widget don't need to be updated when the screen is off and so the service should not run because of battery drain. My question is: How to do it the best way?
I found 2 approaches which would more or less do what I want:
When the first instance of the widget is added to homescreen, it will register a Broadcast Receiver that receives the ACTION_SCREEN_ON and ACTION_SCREEN_OFF intent action from Android OS.
If ACTION_SCREEN_ON is fired, it will start the updater service, otherwise it will stop it. But I'm really unsure if that's a good approach because of the broadcast receiver lifetime.
In the updater service's Runnable, which is executed periodically by the ScheduledExecutorService and actually does the network stuff, I check via PowerManager.isScreenOn(), if the screen is on. If yes, I execute the network code, otherwise not. But what is when the device is in standby? Is this code executed then? What about battery drain here?
Is there maybe a "best practice" approach for what I want to do? I've read alot about the AlarmManager and that it is a very powerful tool. Can you schedule tasks with this in the way, that they are only executed when the display is on?
Thanks for your help.
Best Regards
NiThDi
Your first solution (widget handling screen ON and OFF broadcasts) to the problem is the way to go. Start a new IntentService for a background communication to your application server. Once it finished, send your custom "Completed" broadcast with results and then handle it in your widget.
As an additional benefit it would allow multiple instances of your widget to work from one "source" and would not consume any resources in case user did not add any widgets.
UPDATE As it is impossible to use screen ON/OFF broadcasts with a widget, I would probably use this (AlarmManager with an ELAPSED_REALTIME (not ELAPSED_REALTIME_WAKEUP) alarm) to schedule an (intent) service run. This way your service will not be scheduled when screen is off, but will run periodically when it is on. And if the delay between screen ON and OFF is more than the period it will run immediately on screen coming back ON.
As for your question about broadcast receivers lifetime, as said before, use IntentService for a long running task and then broadcast your results back from it. When you starting a service that call does not block so broadcast receiver will finish in a timely matter.
UPDATE 2 According to this screen OFF does not mean device is sleeping. You already have a solution to this issue by using PowerManager.isScreenOn().
The ELAPSED_REALTIME approach could work I think, but strangly it is not for a small test app I created.
Basically the test app is a widget and a IntentService. The widget only shows the current time, while the Intent Service gets the current time and sends a broadcast which the widget consumes in the onReceive() method and updates itself. The widget is of course a registered receiver for the custom broadcast action.
In the onEnabled() method in the widget, I initialize the alarm for AlarmManager. Now some code:
Widget onEnabled():
#Override
public void onEnabled(Context c) {
super.onEnabled(c);
Intent intent = new Intent(c.getApplicationContext(), SimpleIntentService.class);
PendingIntent intentExecuted = PendingIntent.getService(c.getApplicationContext(), 45451894, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) c.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, 0, 3000, intentExecuted);
}
Widget onReceive():
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals(ACTION_RESP)) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context.getApplicationContext(), Widget.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
for (int appWidgetId : allWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getApplicationContext().getPackageName(), R.layout.widget);
String s = "";
if (intent.hasExtra("msg")) s = intent.getStringExtra("msg");
// Set the text
remoteViews.setTextViewText(R.id.textView1, s);
appWidgetManager.updateAppWidget(allWidgetIds, remoteViews);
}
}
}
SimpleIntentService onHandleIntent():
#Override
protected void onHandleIntent(Intent intent) {
Log.w("TestService", "SimpleIntentService onHandleIntent called.");
String msg = new Date().toGMTString();
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(Widget.ACTION_RESP);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra("msg", msg);
sendBroadcast(broadcastIntent);
}
So, I have tested this on my Nexus 4 running Android 4.2.1 and on an emulator with Android 4.0.4. In both cases the SimpleIntentService gets created and onHandleIntent() gets called every 3 seconds, even when I manually turn the display off.
I have absolutely no idea why the AlarmManager still schedules the alarm, do you have?!
Thank you!
In my app, I place my Service in the foreground to prevent it from being killed by using:
startForeground(NOTIFY_ID, notification);
This also displays the notification to the user (which is great). The problem is that later I need to update the notification. So I use the code:
notification.setLatestEventInfo(getApplicationContext(), someString, someOtherString, contentIntent);
mNotificationManager.notify(NOTIFY_ID, notification);
The question then is: will doing this knock the Service out of it's special foreground status?
In this answer, CommonsWare indicates that this behavior is possible, but he's not sure. So does anyone know the actual answer?
Note: I am aware that a simple way to get out of this question is to repeatedly call startForeground() every time I want to update the notification. I'm looking to know whether this alternative will also work.
To clarify what has been said here:
From what I understand, if you cancel the notification the service
will cease being a foreground service, so keep that in mind; if you
cancel the notification, you'll need to call startForeground() again
to restore the service's foreground status.
This part of the answer suggest it is possible to remove an ongoing Notification set by a Service by using NotificationManager.cancel() on the persistent Notification.
This is not true.
It's impossible to remove a ongoing notification set by startForeground() by using NotificationManager.cancel().
The only way to remove it, is to call stopForeground(true), so the ongoing Notification is removed, which ofcourse also makes the Service stop being in the foreground. So it's actually the other way around; the Service doesn't stop being in the foreground because the Notification is cancelled, the Notification can only be cancelled by stopping the Service being in the foreground.
Naturally one could call startForeground() after this right away, to restore the state with a new Notification. One reason you would want to do this if a ticker text has to be shown again, because it will only run the first time the Notification is displayed.
This behaviour is not documented, and I wasted 4 hours trying to figure out why I couldn't remove the Notification.
More on the issue here: NotificationManager.cancel() doesn't work for me
The RandomMusicPlayer (archived) app at the Android developer site uses NotificationManager to update the notification of a foreground service, so chances are pretty good that it retains the foreground status.
(See setUpAsForeground() and updateNotification() in the MusicService.java class.)
From what I understand, if you cancel the notification the service will cease being a foreground service, so keep that in mind; if you cancel the notification, you'll need to call startForeground() again to restore the service's foreground status.
When you want to update a Notification set by startForeground(), simply build a new notication and then use NotificationManager to notify it.
The key point is to use the same notification id.
Updating the Notification will NOT remove the Service from the foreground status (this can be done only by calling stopForground );
Example:
private static final int notif_id=1;
#Override
public void onCreate (){
this.startForeground();
}
private void startForeground() {
startForeground(notif_id, getMyActivityNotification(""));
}
private Notification getMyActivityNotification(String text){
// The PendingIntent to launch our activity if the user selects
// this notification
CharSequence title = getText(R.string.title_activity);
PendingIntent contentIntent = PendingIntent.getActivity(this,
0, new Intent(this, MyActivity.class), 0);
return new Notification.Builder(this)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.ic_launcher_b3)
.setContentIntent(contentIntent).getNotification();
}
/**
this is the method that can be called to update the Notification
*/
private void updateNotification() {
String text = "Some text that will update the notification";
Notification notification = getMyActivityNotification(text);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(notif_id, notification);
}