I did implement a notification feature in android using the Notification.Builder in Android OREO+. I need to cancel the notification after a certain time frame, if the user has not clicked on the notification. which i completed using the setTimeOutAfter method.
https://developer.android.com/reference/android/app/Notification.Builder.html#setTimeoutAfter(long).
Now, i need to send a message to server that the notification wasn't clicked/timeout has occured. How can i implement this? Is there any notificationTimeout Listener?
There's nothing like a timeout listener but you can use a delete intent for your purpose. You'll need a Broadcast Receiver in order to do something (like calling your server) when the notification gets dismissed.
In code:
class NotificationDismissedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// call your server here
}
}
private fun getNotificationWithDeleteIntent() : Notification{
val deleteIntent = Intent(context, NotificationDismissedReceiver::class.java)
deleteIntent.action = "notification_cancelled"
val onDismissPendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(textTitle)
.setContentText(textContent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setTimeoutAfter(TIMEOUT)
.setDeleteIntent(onDismissPendingIntent)
return builder.build()
}
Related
I am trying to set an alarm for a 15 minute reminder (Testing with 5 seconds first). When I register the receiver in my Manifest, it works perfect. When I try to set it as a dynamic broadcast receiver, I can't get it to work. The onReceive() method doesn't get called at all. I can see this by putting in Log.d() entries
The reason I need to convert it to a dynamic broadcast receiver is because I can't figure out how to access my MainActivity from inside the onReceive() method, as I need to perform some UI tasks like show a notification etc.
I am setting up the AlarmManager like this:
fun snoozeTask(context: Context, taskId: String, snoozeTime: Long) {
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
intent.putExtra("taskId", taskId)
intent.action = AlarmReceiver.SNOOZE_TASK
PendingIntent.getActivity(context, ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
(context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager)?.let { alarmManager ->
val hasPermissions = alarmManager.canScheduleExactAlarms()
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.ELAPSED_REALTIME_WAKEUP, snoozeTime, alarmIntent)
}
}
and then my onReceive method:
class AlarmReceiver : BroadcastReceiver() {
companion object {
const val SNOOZE_TASK = "com.my.app.SNOOZE_TASK"
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED -> {
// reschedule all the exact alarms
//TODO: When the user revokes permission for
// <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
}
SNOOZE_TASK -> {
val taskId = intent.extras?.getString("taskId")
val snoozeTime = intent.extras?.getLong("snoozeTime")
}
else -> Log.d("TAG", "AlarmReceiver::onReceive::ELSE")
}
}
}
Then in my MainActivity I have this:
private var alarmReceiver: AlarmReceiver = AlarmReceiver()
and then in MainActivity onCreate:
registerReceiver(alarmReceiver, IntentFilter(AlarmReceiver.SNOOZE_TASK))
With the same code, this works when I register it in the Manifest. When I try to register in MainActivity, it doesn't work.
Eventually I want to pass some MutableLiveData to the constructor of my AlarmReceiver class so when I receive the taskId, it gets passed back to MainActivity this way.
What am I doing wrong?
Thanks
I have got a periodic work request running foreground. Using the following version of WorkManager
androidx.work:work-runtime-ktx:2.5.0-alpha03
Here is the work request.
val workRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
setConstraints(constraints).build()
And on the worker class, I set it to foreground.
class MyWorker(context, params) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(createForegroundInfo())
//rest of the logic
}
private void createForegroundInfo() {
val cancelIntent = WorkManager.getInstance(context).createCancelPendingIntent(id) //work request id
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle("Test Title")
.addAction(actionIconRes, "Cancel", cancelIntent).build()
return ForegroundInfo(notificationId, notification)
}
}
When I run it, I can see the notification coming up with cancel action. When clicked, the notification disappears momentarily and appears again. Nothing happens on further clicks on cancel action. There is no cancel signalled at all either. What could potentially be a stopper? Any clue?
I think I found the problem. I had nested suspending functions which were causing the Worker to cancel completely. Now that I have changed those functions, it's all working now.
I'm trying to implement an implicit deep link handling in my application, but the following code doesn't work out and onNewIntent in my single activity isn't calling, but always startDestination from navigation graph is opening.
In my navigation graph, I have the following deep link for a fragment
<deepLink
android:id="#+id/deepLink"
app:uri="people/{uuid}" />
Then I added the nav. graph to manifest file between the activity tag
<nav-graph android:value="#navigation/app_graph" />
After I put onNewIntent implementation to MainActivity and it looks like
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
findNavController(R.id.fragmentContainer).handleDeepLink(intent)
}
Creating of a pending intent is happening like:
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
data = Uri.parse("people/$uuid")
}
val pendingIntent = PendingIntent.getActivity(
context,
PENDING_INTENT_REQUEST_CODE,
intent,
0
)
And finally the dialog creation
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
// not important
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
NotificationManagerCompat
.from(context)
.notify(Random.nextInt(), notification)
I think first your scheme is wrong it should be something like <scheme_name>://people/{uuid}
I'm checking my app with the Android Q [beta 6] in order to add all the required changes to be fully-compatible with the last SO. However, I found out that I am using a Receiver to start an Activity from background and due to the last background limitations implemented (https://developer.android.com/preview/privacy/background-activity-starts) the activity is not being opened.
I tried to use both the receiver context and application context to start the activity but in both cases the system shows a toast saying that is not possible to start activity from background.
What I tried on the Receiver...
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context?.applicationContext?.let {
it.startActivity(Intent(it, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
PushUtils.showReceiverCalledNotification(it)
}
}
That way I wanted to start MyActivity and also show a notification when the receiver is called. Instead, I can see the notification but the Activity is never started. It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?
It is very important for the feature to start the activity immediately, so there is a way to continue starting the activity from the receiver?
No, sorry. Use a high-priority notification, so it appears in "heads-up" mode. The user can then rapidly tap on it to bring up your activity.
Due to restrictions, you cannot start activity from background. Instead you can use notifications as CommonsWare suggested and also suggested on the android developer site.
Here's the official documentation that lists situations when it will work and when won't.
https://developer.android.com/guide/components/activities/background-starts
You can use something like this:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
showNotification(context.applicationContext)
} else {
context.applicationContext.startActivity(Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
PushUtils.showReceiverCalledNotification(context)
}
private fun showNotification(context: Context) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(context, MyActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
with(NotificationCompat.Builder(context, "default")) {
setSmallIcon(R.drawable.ic_scan_colored)
setContentTitle("Custom Title")
setContentText("Tap to start the application")
setContentIntent(pendingIntent)
setAutoCancel(true)
manager.notify(87, build())
}
}
}
I am receiving the following crash report from some of my users on devices like the Google Pixel 2, LG Nexus 5X and Nokia 6, 7 or 8 on Android 8+. I am not able to reproduce this crash on an LG V30 with 8.0, Google Pixel with Android 9.0 or emulators with 8.1.
Fatal Exception: android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=null pri=-2 contentView=null vibrate=null sound=null smartAlertCount=0x0 defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6501)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
The only notification for startForeground in my code is a notification that is being displayed during audio playback with android.media.MediaPlayer. I suspect this notification to be the one from the exception, since many users tell me that the MediaPlayer is not working on their phone - but I am not totally sure, since the crash log is not telling exactly where this error occurs.
The Kotlin code for creating this notification looks like this:
class MediaPlayerService : Service() {
private var mediaPlayer: MediaPlayer? = null
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
mediaPlayer = MediaPlayer()
mediaPlayer?.let { mediaPlayer ->
mediaPlayer.setOnPreparedListener(MediaPlayer.OnPreparedListener {
mediaPlayer.start()
showNotification()
})
try {
mediaPlayer.setDataSource(URL)
mediaPlayer.prepareAsync()
} catch (exception: IOException) {
exception.printStackTrace()
}
}
return Service.START_NOT_STICKY
}
fun showNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
val channel = NotificationChannel(
"channel_id",
"Channel",
android.app.NotificationManager.IMPORTANCE_LOW
).apply {
lightColor = Color.GREEN
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
setSound(Settings.System.DEFAULT_NOTIFICATION_URI, null)
setShowBadge(false)
}
notificationManager?.createNotificationChannel(channel)
}
NotificationCompat.Builder(context, "channel_id")
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup("channel_id")
.setGroupSummary(false)
.setColor(Color.GREEN)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.setAutoCancel(true)
.setSmallIcon(R.drawable.icon_small)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.icon_large))
.setContentTitle("Title")
.setContentText("Text")
.setTicker("Text")
.setStyle(NotificationCompat.BigTextStyle().bigText("Text"))
.setNumber(1)
.build()
}
I am using Notification Channels for all of my notifications, including the one from above. I am not able to reproduce any problem with these notifications, as they all seem to work as expected for push notifications - and for the most part for the foreground service, too.
Is there something I am missing?
'mysun' had an observation that reduced the complaints for my foreground service:
The system can not find the specified resources
This is generally due to setSmallIcon(R.drawable.icon_small), which is not yet ready for some circumstances (such as when you start a restart system to send a notification), resulting in an App exception.
Instead, get the application icon from ApplicationInfo; e.g. setSmallIcon(context.getApplicationInfo().icon)
see mysun's fatalerrors.org post here
I had a similar problem, it turned out that my context was null, then I passed the context from the .MyApp main class
PendingIntent broadcast = PendingIntent.getActivity
(context, 222, intent1, PendingIntent.FLAG_MUTABLE);
everything worked.
Just try to find out what value your context has, maybe you have null too