I'm building a video calling app and need to display an incoming call screen. I use a Notification that displays an activity with fullscreen intent mechanism as follows:
private fun createIncomingCallNotification(): Notification {
val fullscreenPendingIntent = IncomingCallActivity.intent() // from splitties.intents module
.toPendingActivity(flags =
PendingIntent.FLAG_UPDATE_CURRENT or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_CLEAR_TOP
)
return NotificationCompat.Builder(this, INCOMING_CALL_NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_baseline_call_24)
.setContentTitle("Incoming call")
.setContentText("(919) 555-1234")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setFullScreenIntent(fullscreenPendingIntent, true)
.setContentIntent(
VideoCallActivity.intent()
.toPendingActivity(flags =
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_CLEAR_TOP
)
)
.setAutoCancel(true)
.setVibrate(DEFAULT_VIBRATE_PATTERN)
.setSound(incomingCallSound)
.build()
}
It is used as a notification for a foreground service which is triggered when the receiver receives an incoming call request.
The fullscreen activity and notification display OK. However, when I accept the call by clicking on the notification, fullscreen activity remains in the history stack (even with all these flags added plus the autoCancel). So then, when I end my call the fullscreen IncomingCallActivity pops up. It remains even when I stop the foreground service associated with the notification.
Any help would be appreciated!
TL;DR:
I'm looking for a auto-cancel-like behavior for a fullScreenIntent accompaniying an annotation. As it's for the system to decide whether the activity is shown, I'd like to have a generic approach to cancel the notification and close the fullscreen activity on any action selection (reject or accept the call). I suspect it should be done with a BroadcastReceiver, but looking for tips on how it should be done in a concise manner.
EDIT:
I've managed to solve this with BroadcastReceiver registered within the fullscreen activity with a IntentFilter. In the notification reject action I just sendBroadcast which then finishes the fullscreen activity.
However still looking for a 'best practices' approach on that.
Related
i am trying to implement notifications in my android application.
when the user clicks on the notification i run the activity if it isn't already running .. but if the activity is already running i don't want to recreate it, i just want to refresh the data by intercepting the intent from the onNewIntent function.
the problem is that every time I click on the notification, the activity is recreated, and the onNewIntent function is not called.
the problem occurs when I create the notification from a service.
but when I create the notification from the same activity that I am going to run, everything works fine.
I searched the internet and tried all the solutions I found, but it still doesn't work
I tried several combinations of intent flags, but nothing works
this is my code
val intent : Intent = Intent(this, HomeActivity::class.java)
finalIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
val pendingIntentFlags : Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
{
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
}
else
{
PendingIntent.FLAG_UPDATE_CURRENT
}
val pendingIntent : PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(finalIntent)
getPendingIntent(0, pendingIntentFlags)
}
val notification = NotificationCompat.Builder(context, type.channelId)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(Notification.CATEGORY_SERVICE)
.setDefaults(Notification.DEFAULT_ALL)
.setAllowSystemGeneratedContextualActions(true)
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.setLocalOnly(true)
.setOngoing(false)
.setSilent(false)
.setSmallIcon(R.drawable.icon_notification)
.setContentIntent(pendingIntent)
.build()
notificationManager?.notify(NOTIFICATION_ID, notification)
Let your notification send a broadcast.
In your activity you should register a dynamic broadcast receiver.
This will allow you to refresh the content in the activity without reloading it.
For the case that the activity is not running, you should define a broadcast receiver in the manifest which starts the activity.
when your activity starts, disable the broadcast receiver and re-enable it when your activity stops.
You can just set launch mode attribute to singleInstance in the manifest on your activity and start it with FLAG_ACTIVITY_NEW_TASK only.
I have an app that has a background service running that listens for events.
One of the events should unlock the phone and bring the app to the foreground.
What approaches here are possible?
I was thinking, would it be possible to send a local notification that is actually high priority so it opens the app automatically?
Currently I try to open apps activity this way:
private fun getIntent(pin: String): Intent = Intent(context, XActivity::class.java).apply {
putExtra(XActivity.EXTRA_SMTH, x)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
private fun showActivity(x: String) {
val intent = getIntent(x)
context.startActivity(intent)
}
This code piece works alright if the app is in the foreground, but does not if the app is in the background.
Any ideas/solutions are welcomed.
At first, if you listened for ACTION_SCREEN_ON or ACTION_SCREEN_ON, make sure to explicitly set your listeners ref.
Secondly, due to background restriction, you cannot start an Activity from background. You have to start a foreground service which you will start when the receiver receives the event. From that service, you can launch your activity with your desired intent.
Foreground service needs a notification. Inside your service, create a notification with your intent like following and call startForeground() with this notification. Also create and register NotificationChannel before if not already.
val fullScreenIntent = Intent(this, XActivity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val notificationBuilder =
NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Launch Activity")
.setContentText("Tap to launch Activity")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_ALARM) // Set your desired category
// Use a full-screen intent only for the highest-priority alerts where you
// have an associated activity that you would like to launch after the user
// interacts with the notification. Also, if your app targets Android 10
// or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
// order for the platform to invoke this notification.
.setFullScreenIntent(fullScreenPendingIntent, true)
val alarmNotification = notificationBuilder.build()
I have 5 activities in my app. Every activity starts the same foreground service.
In onStartCommand method of the service foreground notification is created which unfortunately means that every call of startForegroundService() in any activity plays notification sound (even though the service is already running). How can I create the foreground notification only once or at least how not to play notification sound on successive startForegroundService() calls?
The other related question is: how can I go back to my application when I click the foreground notification? I have 5 activites and I would like to reopen the activity that was the last one the user was interacting with.
#1. before starting the service just check if its already running or not. In that case this will help you https://stackoverflow.com/a/5921190/6413387
#2. To reopen your last opened activity, you need to update the pending intent of your notification. Hope you will find your answer here https://stackoverflow.com/a/20142620/6413387
How can I create the foreground notification only once or at least how not to play notification sound on successive startForegroundService() calls?
You can check if the notification is already visible and show it only if it's not visible. You need to have a reference to the notification PendingIntent and notificationId.
fun isNotificationVisible(context: Context, notificationIntent: Intent, notificationId: Int): Boolean {
return PendingIntent.getActivity(context, notificationId, notificationIntent, PendingIntent.FLAG_NO_CREATE) != null
}
how can I go back to my application when I click the foreground notification?
You need a PendingIntent to open the app from a notification. To open the last activity shown you can remember this using Preferences in the onResume() method of each activity and route the notification into a routing activity that starts the right activity according to the value saved into the preferences.
val intent = Intent(context, RouteActivity::class.java)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setContentIntent(intent)
val notificationManager = NotificationManagerCompat.from(context)
val notification = notificationBuilder.build()
notificationManager.notify(notificationId, notification)
Another way to do this is to update the notification PendingIntent if it's already visible with the last activity shown. In this case you don't have to store any value on Preferences and you don't need a route activity.
I am using code similar to Creating a simple notification to create and show a notification from a network call.
The issue is that I want the activity that responds to the notification to do it's business and then on a backbutton click, put the previously active activity back in the foreground, with it's back stack intact. This is regardless of if the previously active activity was part of my app or somebody elses.
Currently it is following the generated TaskStackBuilder. Leading it back up the app hierarchy and out to the home screen. This is bad UI-design as it breaks the work-flow of anyone using the device, forcing them to manually go back to their app and spending more buttonclicks than necessary. It is also rather unintuitive.
According to the official desing guidelines this is how it should work. The implementation I linked to higher up makes back button have the same functionality as up button should have
It is also a common way to implement it in a plethora of other apps, including official google ones (google-play update notifications come to mind), so there must be a relatively standard way to do this.
Intent intent = new Intent(getApplicationContext(), MainActivity.class)
//add Intent.FLAG_ACTIVITY_CLEAR_TASK flag this will clear all activitys and
//launched activity at top. Means no other activity of this application will be running
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
or
// add Intent.FLAG_ACTIVITY_MULTIPLE_TASK which start one more task your applications
// where activity will not be cleared;
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
Notification n = new Builder(context.getApplicationContext())
.setContentTitle("simple notification title")
.setContentText("simple message")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.addAction(R.drawable.ic_launcher, "And more",pendingIntent ).build();
NotificationManager notificationManager =(NotificationManager) context.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, n);
There are a lot of questions/answers about how to start an application from within your application in Android. But those solutions do not produce the same flow as if an icon was tapped in Android launcher.
For example, I do this (this is used with notifications):
intent = context.getPackageManager().getLaunchIntentForPackage("com.test.startup");
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pendingIntent = PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then when I tap on notification the app is started, however, it is started somewhat differently than when I tap the icon in the App drawer. Specifically: with this approach my main Activity is always created (i.e. onCreate() then onResume() is called). However, if application was already started and then put in background, then starting it from Launcher will only cause onResume() of currently shown activity to be called (not onCreate() on the main one). Is there a way to trigger the same resume flow programmatically from within my app?
To summarize the task: when user taps on notification I need my app to be either started (if it's not already), or brought to the foreground in its current state (if it's in background) and have some data passed to it. The app will then take care of handling/rendering that data.
Your app is behaving the way it supposed to. Even if you try the launch the app from App drawer it will call the same callback. You have to understand the lifecycle. As your activity is in the background onCreate will not get called. But for the handling the data from the notification intent you should utilize callback method OnNewIntent() in activity. You should override this method and extract the data the from the new intent and should update UI. After onNewIntent onresume will be called.
I hope this solves your problem.
Here is my onPause code which works the way you expected i.e when user clicks on the notification it doesnt call onCreate again:
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
intent = new Intent(getApplicationContext(), PlayerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pIntent = PendingIntent.getActivity(getBaseContext(), 0, intent,0);
NotificationCompat.Builder noti =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_media_play)
.setContentTitle("Nepali Music And more")
.setContentText("Playing");
noti.setContentIntent(pIntent);
noti.setAutoCancel(true);
noti.setOngoing(true);
Notification notification = noti.getNotification();
notificationManager.notify(1, notification);
Focus mainly on the intent flags
You want to use the intent flags Intent.FLAG_ACTIVITY_CLEAR_TOP to find your activity and clear the stack above it. You also need the Intent.FLAG_ACTIVITY_SINGLE_TOP flag to prevent your activity from being recreated (to resume).
The Intent.FLAG_ACTIVITY_SINGLE_TOP is necessary since by default, the launch mode is "standard" which lets you create multiple instances of your activity. If you were to set your launch mode to SingleTop, then this flag own't be necessary