I have set up a NotificationListenerService which listens for notifications. I need to modify the sound/alert tone of the notifications which is caught in the method below :
override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
// Modify the tone here and notify ( the notification ) it again
}
What I have tried so far ( does not play the sound ):
notification.notification.defaults = android.app.Notification.DEFAULT_VIBRATE
notification.notification.sound = Uri.parse(sharedPreferences.getString(getString( R.string.ringtone_key ) , Settings.System.DEFAULT_NOTIFICATION_URI.toString() ))
manager.notify( RECREATE_NOTIFICATION_ID , notification.notification )
My question goes here:
How can I modify the sound/alert tone of the StatusBarNotification caught in the above method and display it to the user? Do I need to repost/recreate it again?
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O)
{
if (soundUri != null)
{
// Changing Default mode of notification
notificationBuilder.setDefaults(Notification.DEFAULT_VIBRATE)
// Creating an Audio Attribute
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
// Creating Channel
val notificationChannel = NotificationChannel("CH_ID", "Testing_Audio", NotificationManager.IMPORTANCE_HIGH)
notificationChannel.setSound(soundUri, audioAttributes)
mNotificationManager.createNotificationChannel(notificationChannel)
}
}
To add some context to ismail alaoui's answer - what you did should probably work for pre-Oreo android devices, but for Oreo and above, you need to create a notification channel, to which the custom sound will be assigned. Refer to https://developer.android.com/guide/topics/ui/notifiers/notifications.
Please also remember, that user might change the sound of the notification channel at any moment :)
So only question remaining - which Android version are you testing your solution on?
Related
I'm trying to implement a custom notification sound in my application.
I have written the following code, but the application plays only default sound and not the custom sound i've added in raw folder. Upon receiving the notification, the logs doesn't even throw any error or exception as to why it isn't playing the custom sound. I tried searching online and tried following different approaches but to no avail.
Please let me know where am i going wrong.
Edit: Can someone post the code for it, i cant seem to find anything that works
Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.notify);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("MyCuS Notification", "My Notification", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager = getSystemService(NotificationManager.class);
AudioAttributes.Builder audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
channel.setSound(Uri.parse("android.resources://" + getPackageName() + "/" + R.raw.bg_reminder_alarm),audioAttributes.build());
manager.createNotificationChannel(channel);
}
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(MainActivity.this, "MyCuS Notification");
builder.setContentTitle("MyTitle");
builder.setContentText("TESTING");
builder.setSmallIcon(R.drawable.ic_launcher_background);
builder.setAutoCancel(true);
builder.setSound(Uri.parse("android.resources://" + getPackageName() + "/" + R.raw.bg_reminder_alarm));
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(MainActivity.this);
managerCompat.notify(1, builder.build());
}
});
}
Edit 2: I tried deleting existing channel and sending notification to create new channel, when newly created the description of the channel changes after sending second notification, it is as if the channel is overridden or deleted and new default channel is created.
Since Android Oreo / 8 the Notificationsound is coming from the Channel and can only be set the first time you add the channel via your channel.setSound().
If you want to change it later on you need to delete the channel and then re-add it to the system. The user will be warned about that behaviour though (App deleted channels X amount of times).
https://developer.android.com/guide/topics/ui/notifiers/notifications#ManageChannels
If you want to have a customsound each and every time, you need a ForegroundService without a channelsound for it's foreground notification (setSound(null)) and then use the MediaPlayer on the Notificationstream to play the custom sound.
My app is simple
Take Time from the user(I know how to do it)
schedule A notification(I know how to send notification)
Now I just want to know that How can I send this notification even user removes it from recent apps.
Tried Solutions-
AlarmManager, BroudcastReceiver, Service, Intent Service,
But all are work only when the app is present in RAM (recent apps list Of Mobile).
How can I do that just tell me the topic. I don't need code.
I will study that.
you can use WorkManger to schedule tasks.
The WorkManager API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
check Google documentation here.
for notifications, you can send a notification in your work manager class. learn more here.
these hints should be enough. let me know if you need more clarifications.
As you have mentioned that AlarmManager and others did not work for you, I tested what you are trying to achieve with JobScheduler.
It worked for me, and it worked even after removing the app from the recent apps list.
I tried it on Android 10 emulator.
You have asked for topics / references to study as the answer.
So first I'll mention the references I used.
Here is the code lab of JobScheduler and it's really helpful : https://codelabs.developers.google.com/codelabs/android-training-job-scheduler/
Here is a good reference about creating multiple scheduled jobs : https://android-developers.googleblog.com/2017/10/working-with-multiple-jobservices.html
Here is what I tried.
NotificationJobService.kt
private const val NOTIF_CHANNEL_ID = "primary_notification_channel"
private const val NOTIF_CHANNEL_NAME = "Job Service notification"
class NotificationJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
// Get Notification Manager
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Create Notification Channel if device OS >= Android O
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(NOTIF_CHANNEL_ID, NOTIF_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).let {
notificationManager.createNotificationChannel(it)
}
}
// Create PendingIntent with empty Intent
// So this pending intent does nothing
val pendingIntent = PendingIntent.getActivity(this, 0, Intent(), PendingIntent.FLAG_ONE_SHOT)
// Configure NotificationBuilder
val builder = NotificationCompat.Builder(this, NOTIF_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Title")
.setContentText("Message")
.setAutoCancel(true)
.setContentIntent(pendingIntent)
// Make the Notification
notificationManager.notify(0, builder.build())
// False to let system know that the job is completed by the end of onStartJob(),
// and the system calls jobFinished() to end the job.
return false
}
override fun onStopJob(params: JobParameters?): Boolean {
// True because if the job fails, you want the job to be rescheduled instead of dropped.
return true
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Job ID must be unique if you have multiple jobs scheduled
var jobID = 0
// Get fake user set time (a future time 1 min from current time)
val ( userSetHourOfDay, userSetMinute ) = getMockUserSetTime()
val timeToWaitBeforeExecuteJob = calculateTimeDifferenceMs(userSetHourOfDay, userSetMinute)
(getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler).run {
schedule(
JobInfo.Builder(
jobID,
ComponentName(baseContext, NotificationJobService::class.java)
)
// job execution will be delayed by this amount of time
.setMinimumLatency(timeToWaitBeforeExecuteJob)
// job will be run by this deadline
.setOverrideDeadline(timeToWaitBeforeExecuteJob)
.build()
)
}
}
// Returns a pair ( hourOfDay, minute ) that represents a future time,
// 1 minute after the current time
private fun getMockUserSetTime() : Pair<Int, Int> {
val calendar = Calendar.getInstance().apply {
// add just 1 min from current time
add(Calendar.MINUTE, 1)
}
return Pair(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE))
}
// Calculate time difference relative to current time in ms
private fun calculateTimeDifferenceMs(hourOfDay: Int, minute: Int) : Long {
val now = Calendar.getInstance()
val then = (now.clone() as Calendar).apply {
set(Calendar.HOUR_OF_DAY, hourOfDay)
set(Calendar.MINUTE, minute)
}
return then.timeInMillis - now.timeInMillis
}
}
I used setMinimumLatency(timeToWaitBeforeExecuteJob) and setOverrideDeadline(timeToWaitBeforeExecuteJob) constraints when scheduling the job so that, the job will be executed at exact time we want it to run.
I ran the app once, go back and removed the app from recent apps list.
Then I suspended the device, and after 1 minute, I heard the notification sound.
When I resumed the device and checked, the expected notification was there.
You should consider Remon Shehatta's answer as well. Because it seems
WorkManager sits on top of JobScheduler and AlarmManager, and picks
the right one based on device's API level. So it may be better to use
WorkManager if you are targeting API levels older than 21.
But as you mentioned that AlarmManager did not work for you, you should
experiment and choose the right one.
Please update us with your findings.
I have an Android app, and am trying to send a notification to a specific device on which it is installed, using the Firebase Messaging API from a Firebase Function. But the notification never shows up. However, sending manually from the Firebase Console does make a notification show up successfully on the same device. Here is the relevant Firebase Function code:
var message = {
notification: {
title: notifTitle,
body: notifBody,
sound: 'default',
android_channel_id: 'update_channel',
channel_id: 'update_channel'
// tag:
}
};
var options = {}
console.log(message);
console.log('registrationToken: ' + registrationToken);
// Send a message to the device corresponding to the provided
// registration token.
admin.messaging().sendToDevice([registrationToken], message, options)
.then((response) => {
console.log(response);
...
Which logs the following when invoked, appearing to have sent the notification successfully:
{ results: [ { messageId: '0:1544572985549904%5be491645be49164' } ],
canonicalRegistrationTokenCount: 0,
failureCount: 0,
successCount: 1,
multicastId: 5825519571250606000 }
In my Android app I've created the update_channel in the main activity's onCreate() (because, from what I've gathered, Android Oreo API 26+ requires a specific channel...):
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String id = getString(R.string.update_notification_channel);
CharSequence name = getString(R.string.app_name);
String description = getString(R.string.app_name);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(id, name, importance);
channel.setImportance(NotificationManager.IMPORTANCE_HIGH);
//channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
And also setting it as the default channel in the Manifest:
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="update_notification_channel"/>
I don't know if the problem is channel-related, or if I'm missing a step somewhere, or what.
For notifications to work while the app is in the foreground or background, you must implement and register a FirebaseMessagingService in your Android app, to receive calls on onMessageReceived(), then create notifications with the NotificationManager in there manually when a notification message comes in.
From the documentation:
Firebase notifications behave differently depending on the foreground/background state of the receiving app. If you want foregrounded apps to receive notification messages or data messages, you’ll need to write code to handle the onMessageReceived callback.
When creating a bundled notification using setGroup() and setGroupSummary() I am having some strange issues regarding the behaviour of the notifications.
So, as a reference. This example contains the issue:
var isFirstNotificationInGroup = true
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notificationManager.activeNotifications.forEach {
if (it.notification.group == groupId) {
isFirstNotificationInGroup = false
}
}
}
val builder = NotificationCompat.Builder(this, channelId).apply {
color = resources.getColor(R.color.colorAccent)
priority = NotificationCompat.PRIORITY_MAX
setSmallIcon(R.drawable.ic_dotoo_logo)
setContentTitle(title)
setContentText(body)
setStyle(NotificationCompat.BigTextStyle()
.bigText(body))
setAutoCancel(true)
setCategory(NotificationCompat.CATEGORY_SOCIAL)
setGroup(groupId)
setGroupSummary(isFirstNotificationInGroup)
}
< ... >
with(NotificationManagerCompat.from(this)) {
notify(notificationId, builder.build())
}
What happens?
The first notification will be shown as it should. So no issues here.
Then, when we show the second notification. It replaces the first one. This shouldn't happen. And no, it is not due to the notification ID. That's not related to this as far as I know.
But, when we show a third (or more) notification, the bundle works as expected and shows two (or more) bundled notifications. But the first one is... gone.
Thanks in advance for helping me.
I have fixed it by creating a seperate summary notification when isFirstNotificationInGroup is true.
This will be send just before the 'real' notification will be send.
Ever since adding support for Android O, Android devices running O receive an alert (tone or vibration) whenever my app's media controls notification gets updated (metadata or playback state changes). I'm looking for a way to disable this alert since it's not applicable to media style notifications.
Here is the code I use to create a media controls notification:
MediaDescriptionCompat description = metadata.getDescription();
String artistName = metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
String albumName = metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM);
Bitmap largeIcon = BitmapFactory.decodeResource(playbackService.getResources(),
R.drawable.vector_global_defaultsong);
NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(playbackService, NotificationHelper.CHANNEL_MEDIA_CONTROLS)
.setColor(ContextCompat.getColor(playbackService, R.color.colorAccent))
.setSmallIcon(R.drawable.logo_light_filled)
.setContentTitle(description.getTitle())
.setContentText(playbackService.getString(R.string.song_list_subtitle_format,
artistName, albumName))
.setContentIntent(createContentIntent())
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(playbackService,
PlaybackStateCompat.ACTION_STOP))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOngoing(playbackState.getState() == PlaybackStateCompat.STATE_PLAYING)
.setLargeIcon(largeIcon);
notificationBuilder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
// show previous, play/pause, and next in compact view
.setShowActionsInCompactView(addActions(notificationBuilder))
.setMediaSession(sessionToken));
showNotification(notificationBuilder.build());
. . .
private void showNotification(final Notification notification) {
if (!started) {
mediaController.registerCallback(mediaControllerCallback);
playbackService.startForeground(NOTIFICATION_ID, notification);
started = true;
} else {
notificationManager.notify(NOTIFICATION_ID, notification);
}
if (playbackState.getState() == PlaybackStateCompat.STATE_PAUSED) {
playbackService.stopForeground(false);
}
}
Here is the code I use to create the notification channel:
public static final String CHANNEL_MEDIA_CONTROLS = "media_controls";
public static void createNotificationChannels(Context context) {
if (android.os.Build.VERSION.SDK_INT >= 26) {
NotificationChannel mediaControlsChannel = new NotificationChannel(CHANNEL_MEDIA_CONTROLS,
context.getString(R.string.notification_channel_media_controls),
NotificationManager.IMPORTANCE_HIGH);
mediaControlsChannel.setShowBadge(false);
getNotificationManager(context).createNotificationChannel(mediaControlsChannel);
}
}
Update: Setting the showBadge to false on the notification channel doesn't appear to do anything. I still receive a badge on the app icon when the media controls notification is shown. So it looks like the notification channel attributes that I set are not being applied.
Per the Migrating MediaStyle notifications to Android O, you should be using IMPORTANCE_LOW, which does not contain any sound - IMPORTANCE_HIGH channels have sound associated with them.
Android already reorders MediaStyle notifications higher in the tray, so using a higher importance is not necessary as it was on previous versions of Android.
NOTE: After changing the importance, you need to clear app data or reinstall the app in order to have this change take effect in notification channel.