You can still offer sound and vibration customization in your app, but it requires a different approach. In short, the idea is to play sound and vibration manually in Android O instead of using the notification channel (it's easier than it seems).
This is how I did it:
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
// builder.setSmallIcon(...)
// builder.setContentTitle(...)
// builder.setContentText(...)
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// play vibration
vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(VibrationEffect.createWaveform(vibrationPattern, -1));
// play sound
Intent serviceIntent = new Intent(context, SoundService.class);
serviceIntent.setAction("ACTION_START_PLAYBACK");
serviceIntent.putExtra("SOUND_URI", soundUri.toString());
context.startForegroundService(serviceIntent);
// the delete intent will stop the sound when the notification is cleared
Intent deleteIntent = new Intent(context, SoundService.class);
deleteIntent.setAction("ACTION_STOP_PLAYBACK");
PendingIntent pendingDeleteIntent =
PendingIntent.getService(context, 0, deleteIntent, 0);
builder.setDeleteIntent(pendingDeleteIntent);
} else {
builder.setVibrate(vibrationPattern);
builder.setSound(soundUri);
}
notificationManager.notify(notificationId, builder.build());
SoundService.class is the place where I play the sound with MediaPlayer:
public class SoundService extends Service {
MediaPlayer mMediaPlayer;
@Override
public IBinder onBind(Intent intent) {
return null;
}
public int onStartCommand(Intent intent, int flags, int startId) {
// foreground notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this, otherChannelId);
builder.setSmallIcon(...)
.setContentTitle(...)
.setContentText(...)
.setAutoCancel(true);
startForeground(foregroundNotificationId, builder.build());
}
// check action
String action = intent.getAction();
switch (action) {
case "ACTION_START_PLAYBACK":
startSound(intent.getStringExtra("SOUND_URI"));
break;
case "ACTION_STOP_PLAYBACK":
stopSound();
break;
}
// service will not be recreated if abnormally terminated
return START_NOT_STICKY;
}
private void startSound(String uriString) {
// parse sound
Uri soundUri;
try {
soundUri = Uri.parse(uriString);
} catch (Exception e) {
cleanup();
return;
}
// play sound
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
cleanup();
}
});
}
try {
mMediaPlayer.setDataSource(this, soundUri);
mMediaPlayer.prepareAsync();
} catch (Exception e) {
cleanup();
}
}
private void stopSound() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
cleanup();
}
private void cleanup() {
stopSelf();
}
}
Recommendations
- Create your notification channel with IMPORTANCE_DEFAULT (for the user this is 'High'), a null sound (
setSound(null,null)
) and a null vibration (setVibrationPattern(null)
) and explain in the channel description that this is the recommended setting in order to avoid conflicts with the app's own customization.
- Turn the whole thing into your favor: instead of removing a feature, give users a new one. You can give them the chance to use your customization features or the notification channel features (for example, you can check the current channel importance and depending on the level you can use one thing or the other).
Foreground notification
Starting Android O, services started from the background must be started as foreground services. This means SoundService needs a foreground notification. You have some options for this:
Create a nice foreground notification with a button that says 'Stop playback' so that the user can stop the sound without removing the notification that started it.
Create a simple notification and send it to a disabled channel (you can create disabled channels if you create them with IMPORTANCE_NONE). Doing this, the default system 'App is running in the background' notification will appear instead of your foreground notification, but users can hide this notification from the status bar if they want.
EDIT: in Android 8.1 it seems that creating a disabled channel with IMPORTANCE_NONE is not useful, as the channel will be enabled automatically when you send a notification. It may be better to create it with IMPORTANCE_LOW from the beginning and let users change the importance if they want.
And then I found one solution for update notification channel sound only Thanks to God!!
I have create Github demo for update different sound in a single notification channel. please check this link => [https://github.com/TejasTrivedi1996/NotificationChannels](https://github.com/TejasTrivedi1996/NotificationChannels) – Tejas Trivedi Apr 20 '19 at 21:12