How to press ON a foreground service Notification? - android

I have a foreground service notification and i want when the user closes the app and presses the notification to open the application again.I implemented an
onclicklistener with onClick() method but it did nothing.If that is not possible i want the "Play" button to be shown in the notification because it is only being shown when i expand the notification.
Here is the service class:
public class MyForeGroundService extends Service {
public MyForeGroundService() {
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public void onCreate() {
super.onCreate();
Log.d(TAG_FOREGROUND_SERVICE, "My foreground service onCreate().");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
switch (action) {
case ACTION_START_FOREGROUND_SERVICE:
startForegroundService();
Toast.makeText(getApplicationContext(), "Foreground service is started.", Toast.LENGTH_LONG).show();
break;
case ACTION_STOP_FOREGROUND_SERVICE:
stopForegroundService();
Toast.makeText(getApplicationContext(), "Foreground service is stopped.", Toast.LENGTH_LONG).show();
break;
case ACTION_PLAY:
Toast.makeText(getApplicationContext(), "You click Play button.", Toast.LENGTH_LONG).show();
break;
case ACTION_PAUSE:
Toast.makeText(getApplicationContext(), "You click Pause button.", Toast.LENGTH_LONG).show();
break;
}
}
return super.onStartCommand(intent, flags, startId);
}
/* Used to build and start foreground service. */
private void startForegroundService() {
Log.d(TAG_FOREGROUND_SERVICE, "Start foreground service.");
// Create notification default intent.
Intent intent = new Intent();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
// Create notification builder.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Make notification show big text.
NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
bigTextStyle.setBigContentTitle("Music player implemented by foreground service.");
bigTextStyle.bigText("Android foreground service is a android service which can run in foreground always, it can be controlled by user via notification.");
// Set big text style.
builder.setStyle(bigTextStyle);
builder.setWhen(System.currentTimeMillis());
builder.setSmallIcon(R.mipmap.ic_launcher);
Bitmap largeIconBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.strava);
builder.setLargeIcon(largeIconBitmap);
// Make the notification max priority.
builder.setPriority(Notification.PRIORITY_MAX);
// Make head-up notification.
builder.setFullScreenIntent(pendingIntent, true);
// Add Play button intent in notification.
Intent playIntent = new Intent(this, MyForeGroundService.class);
playIntent.setAction(ACTION_PLAY);
PendingIntent pendingPlayIntent = PendingIntent.getService(this, 0, playIntent, 0);
NotificationCompat.Action playAction = new NotificationCompat.Action(android.R.drawable.ic_media_play, "Play", pendingPlayIntent);
builder.addAction(playAction);
// Build the notification.
Notification notification = builder.build();
// Start foreground service.
startForeground(1, notification);
}
private void stopForegroundService() {
Log.d(TAG_FOREGROUND_SERVICE, "Stop foreground service.");
// Stop foreground service and remove the notification.
stopForeground(true);
// Stop the foreground service.
stopSelf();
}
}

So to add an "onClick" effect to a notification you should use the ".setContentIntent(pendingIntent)" method in the NotificationCompat.Builder.
For the play button to always be visible is a little bit more tricky from what I read, some say that "setPriority(Notification.PRIORITY_MAX) + setWhen(0)" can solve this, others say that it depends on device and if you have other notifications, are you connected to USB etc and that there is no single working solution for this.

Related

Android Java get MainActivity from sub-module in app project at runtime in foreground service

I am writing an audio player type app with Ionic and Capacitor, everything is fine.
Setup:
My application is playing audio on Android.
I have added foreground service to my plugin (required by Android to play audio if you leave the app, so you can show a notification to start/stop).
This is all fine.
Problem:
What isn’t working is getting access to the MainActivity from app from within module capacitor-plugin-remote-audio.MyForegroundService in the project. Is this even possible?
Illustrated screenshot to show the problem:
Code block that's relevant
/* Used to build and start foreground service. */
public void startForegroundService()
{
Log.d(TAG, "Start foreground service.");
// Create notification default intent to open the MainActivity from Capacitor app when tapped.
Intent intent = new Intent(this, "What goes here to get the MainActivity");
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mainAppIntent, 0);
createNotificationChannel();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setShowWhen(false)
.setSmallIcon(android.R.drawable.ic_media_play) // TODO: fix this to use the app icon.
.setContentTitle("Content Title");
if (mainAppPendingIntent != null) {
builder.setContentIntent(mainAppPendingIntent);
}
addPlayAndPauseButtons(builder);
startForeground(1, builder.build());
}
When starting the service:
Add an extra of the package name (probably don't hard code, but whatever):
// Start the foreground service + show required notification.
String packageName = getActivity().getPackageName();
Intent intent = new Intent(getContext(), MyForegroundService.class);
intent.putExtra(MyForegroundService.EXTRA_Top_Level_Package_Name, packageName);
intent.setAction(MyForegroundService.ACTION_START);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getContext().startForegroundService(intent);
} else {
getContext().startService(intent);
}
In the service:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent != null)
{
String action = intent.getAction();
switch (action)
{
case ACTION_START:
String packageName = intent.getExtras().get(EXTRA_Top_Level_Package_Name).toString();
startForegroundService(packageName);
break;
case ACTION_STOP:
stopForegroundService();
break;
}
}
return super.onStartCommand(intent, flags, startId);
}
Start method
/* Used to build and start foreground service. */
public void startForegroundService(String packageName)
{
Log.d(TAG, "Start foreground service.");
// Create notification default intent to open the MainActivity from Capacitor app when tapped.
Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
createNotificationChannel();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setShowWhen(false)
.setSmallIcon(android.R.drawable.ic_media_play) // TODO: fix this to use the app icon.
.setContentTitle("Content Title")
.setContentIntent(pendingIntent);
addPlayAndPauseButtons(builder);
startForeground(1, builder.build());
}
You are trying to open an activity from another module. Check this answer and could solve your problem.

Foreground service is destroyed after receiving the pending intent

I am trying to write a music player for android and am using foreground service to run the music player. I am sending a pendingIntent from the UI controls to the service to play songs.
After receiving the intent, onStartCommand and onDestroy are called immediately. I am not sure how to stop the onDestroy the call.
I tried changing the pending intent to startService/ContextCompat.startForegroundService but the issue still persists.
Service:
import android.app.Service;
public class PlayerService extends Service{
private TransportControls transportControls;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mediaSessionManager == null) {
initMediaSession();
}
handleIntent(intent);
return START_STICKY;
}
private initMediaSession(){
...
transportControls = mediaSession.getController().getTransportControls();
mediaSession.setCallback(new MediaSessionCompat.Callback() {
// Implement callbacks
#Override
public void onPlay() {
play();
}
...
}
handleIntent(Intent intent){
// Initial checks
if(/* action in intent is play*/) transportControls.play();
}
private void play(SongInfo songInfo){
...
buildNotificaiton(//play action);
}
private void buildNotification(){
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
this, "default").setContentIntent(intent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setStyle(new MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(0, 1, 2))
.setContentText(StringUtils.parseArtists(songInfo.artists()))
.setContentTitle(songInfo.displayName())
.setContentInfo(songInfo.album())
.setSound(null);
NotificationManager notificationManager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("default",
"Bhatki media notification",
NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(
"Notification displayed when music is being played. This notification is "
+ "required for the music to play.");
notificationManager.createNotificationChannel(channel);
}
// Execution is reaching this line.
startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
}
In activity:
Intent intent = new Intent();
intent.setClass(context, PlayerService.class);
intent.setAction(action);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// one approach
ContextCompat.startForegroundService(context, intent);
// Different approach
// PendingIntent pendingIntent = PendingIntent
// .getService(serviceContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// try {
// pendingIntent.send(serviceContext, 0, intent);
// } catch (CanceledException e) {
// Log.d(TAG, "sendIntent: failed");
I also added that android foreground service permission in the manifest:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
I am expecting the notification and service to persist. Can't figure out why OnDestroy is called.
Once the service has been created, the service must call its startForeground() method within five seconds.
Apps that target Android 9 (API level 28) or higher and use foreground services must request the FOREGROUND_SERVICE permission
foreground service
Edit: I can't tell the problem from your code but from my experience, the notification block should be in override fun onCreate()

Android: Keep asynctask even after app is killed

I have this code below that I'm using to test the life cycle of the asynctask. What I want to do is to let the asynctask finish even if the app is forcibly closed by the user. It shows a notification which increments the progress every second. The asynctask is called from a Fragment. However, when I forcibly terminate the app by pressing all apps then swiping it off, the notification stops and no longer increments. See my code below.
Is this the case or am I doing something wrong?
Also, the reason I'm using asynctask is because I want to allow the user to cancel the task whenever possible. I believe this can't be done with IntentService? (Correct me if I'm wrong on this).
Appreciate any help.
UPDATE: I am now trying foreground service as suggested by #John Wick and #Abishek. However, I am not able to stop the service the way I want it to. When I click stop, the loop goes on and does not stop.
public class ForegroundService extends Service {
boolean isCancelled = false;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals("Start")) {
Log.d("Action", "Start");
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction("Start");
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Intent closeIntent = new Intent(this, ForegroundService.class);
closeIntent.setAction("Stop");
PendingIntent pCloseIntent = PendingIntent.getService(this, 0, closeIntent, 0);
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
NotificationManagerCompat manager = (NotificationManagerCompat.from(this));
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentTitle("Truiton Music Player");
builder.setTicker("Truiton Music Player");
builder.setContentText("My Music");
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false));
builder.setContentIntent(pendingIntent);
builder.addAction(android.R.drawable.ic_media_pause, "Stop", pCloseIntent);
startForeground(1, builder.build());
for (int i = 0; i < 10; i++) {
Log.d("Service", String.valueOf(i));
builder.setProgress(9, i, false);
if (isCancelled) {
builder.setContentTitle("Cancelled");
builder.setContentText("");
builder.setProgress(0, 0, false);
builder.mActions.clear();
manager.notify(1, builder.build());
} else {
if (i == 9) {
builder.setContentTitle("Done");
builder.setContentText("");
builder.setProgress(0, 0, false);
manager.notify(1, builder.build());
} else {
manager.notify(1, builder.build());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
stopForeground(true);
} else {
Log.d("Action", "Start");
isCancelled = true;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
}
You need to use a Foreground Service to make it work even if app is closed. See this link for a demo implementation.
http://www.truiton.com/2014/10/android-foreground-service-example/
What you need here is Service and yes it can be stopped when you no longer need it, the advantage over AysncTask is even when you terminate your app from task manager you service whon't get affected.
To stop a service you will need to call
stopService(new Intent(//Service));
Now if you want to show notification which updates a counter then use foreground service for this.
https://developer.android.com/guide/components/services.html#Foreground
You can Keep asynctask even after app is killed but when your doInBackground work finished and onPostExecute method work on that time you need to require available object of context otherwise app will crashed.

Cancelling a service from a notification button

I'm trying to cancel an upload service from my progress notification using the 'cancel' button.
But I cannot get this working.
Notification is created
//Intent class changes from first activity then to service when it's uploading.
Intent cancel = new Intent(intentClass, BaseUploadService.class);
PendingIntent cancelUploadIntent = PendingIntent.getBroadcast(intentClass, 0, cancel, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder
.setSmallIcon(R.drawable.notificationicon)
.setContentTitle(title)
.setContentText(filename)
.setAutoCancel(totalAmount > uploadedAmount)
.setProgress((int) totalAmount, (int) uploadedAmount, false)
.addAction(R.string.assignment_icon_group, intentClass.getString(R.string.cancel), cancelUploadIntent)
;
return builder.build();
Listen for intent from notification button.
public class BaseUploadService extends IntentService {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.debug("onStartCommand - BaseUploadService");
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
public BaseUploadService(String name) {
super(name);
}
#Override
protected void onHandleIntent(Intent intent) {
}
}
Nothing is ever received in the service.
+ there could be multiple services.
Change PendingIntent.getBroadcast() to PendingIntent.getService().
If after that change the service is still not started, try building the notification with setContentIntent instead of addAction.
Check your manifest to confirm that BaseUploadService is declared correctly.

Do not show notification if it is already shown

In my application I want show a notification in some cases.
When notification is active I do not want to create notification again.
I have activity recognition in my app and when it's detected that I am in car it starts to sound notification every second.
How could I prevent a new build notification if there is at least one active notification there?
Here is my code what I tried:
Intent closeIntent;
Intent showIntent;
if (isStart){
closeIntent = new Intent(this, SwitchButtonListener1.class);
} else {
closeIntent = new Intent(this, SwitchButtonListener2.class);
}
closeIntent.setAction("No");
PendingIntent pendingIntentClose = PendingIntent.getBroadcast(this, 0,
closeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action closeAction = new NotificationCompat.Action(R.drawable.btn_close_gray, "No", pendingIntentClose);
if (isStart){
showIntent = new Intent(this, SwitchButtonListener1.class);
} else {
showIntent = new Intent(this, SwitchButtonListener2.class);
}
showIntent.setAction("Yes");
PendingIntent pendingIntentShow = PendingIntent.getBroadcast(this, 0,
showIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action showAction = new NotificationCompat.Action(R.drawable.ic_tick, "Yes", pendingIntentShow);
Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_stat_milebox)
.setContentTitle(title)
.setContentText(message)
.addAction(showAction)
.addAction(closeAction);
builder.setSound(alarmSound);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(100, builder.build());
Though it is an old question, but I think this answer might help others in the future:
In a case like this, when the user needs to be notified only once and the event is ongoing then using .setOnlyAlertOnce(true) and setOngoing(true) with the builder will solve the problem.
Documentation:
setOnlyAlertOnce(true): Set this flag if you would only like the sound, vibrate and ticker to be played if the notification is not already showing.
setOngoing(true): Set whether this is an ongoing notification. Ongoing notifications cannot be dismissed by the user, so your application or service must take care of canceling them. They are typically used to indicate a background task that the user is actively engaged with (e.g., playing music) or is pending in some way and therefore occupying the device (e.g., a file download, sync operation, active network connection).
Notification notification = new NotificationCompat.Builder(this, notificationChannel.getId())
.....
.....
.setOngoing(true)
.setOnlyAlertOnce(true)
.....
.....
.build();
Objects.requireNonNull(notificationManager).notify(notificationId, notification);
You can try the following as a sketch:
public class MediaNotificationManager extends BroadcastReceiver {
private final NotificationManager mNotificationManager;
private Context ctx;
private boolean mStarted = false;
public MediaNotificationManager(Context ctx) {
mCtx = ctx;
mNotificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
/**
* Posts the notification and starts tracking the session to keep it
* updated. The notification will automatically be removed if the session is
* destroyed before {#link #stopNotification} is called.
*/
public void startNotification() {
if (!mStarted) {
// The notification must be updated after setting started to true
Notification notification = createNotification();
if (notification != null) {
mStarted = true;
}
}
}
/**
* Removes the notification and stops tracking the session. If the session
* was destroyed this has no effect.
*/
public void stopNotification() {
if (mStarted) {
mStarted = false;
try {
mNotificationManager.cancel(NOTIFICATION_ID);
} catch (IllegalArgumentException ex) {
// ignore if the receiver is not registered.
}
}
}
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
LogHelper.d(TAG, "Received intent with action " + action);
switch (action) {
//do something with this.
}
}
private Notification createNotification() {
//create and return the notification
}
}
For a bit more read this:
I also used this notification in my code:
https://github.com/googlesamples/android-UniversalMusicPlayer/blob/master/mobile/src/main/java/com/example/android/uamp/MediaNotificationManager.java

Categories

Resources