package com.tanguyantoine.react; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.content.res.Resources; import android.os.IBinder; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v7.app.NotificationCompat; import android.view.KeyEvent; import com.facebook.react.bridge.ReactApplicationContext; public class MusicControlNotification { protected static final String REMOVE_NOTIFICATION = "music_control_remove_notification"; protected static final String MEDIA_BUTTON = "music_control_media_button"; protected static final String PACKAGE_NAME = "music_control_package_name"; private final ReactApplicationContext context; private int smallIcon; private NotificationCompat.Action play, pause, stop, next, previous; public MusicControlNotification(ReactApplicationContext context) { this.context = context; Resources r = context.getResources(); String packageName = context.getPackageName(); // Optional custom icon with fallback to the play icon smallIcon = r.getIdentifier("music-control-icon", "drawable", packageName); if(smallIcon == 0) smallIcon = r.getIdentifier("play", "drawable", packageName); } public void updateActions(long mask) { play = createAction("play", "Play", mask, PlaybackStateCompat.ACTION_PLAY, play); pause = createAction("pause", "Pause", mask, PlaybackStateCompat.ACTION_PAUSE, pause); stop = createAction("stop", "Stop", mask, PlaybackStateCompat.ACTION_STOP, stop); next = createAction("next", "Next", mask, PlaybackStateCompat.ACTION_SKIP_TO_NEXT, next); previous = createAction("previous", "Previous", mask, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, previous); } public void show(NotificationCompat.Builder builder, boolean isPlaying) { // Add the buttons builder.mActions.clear(); if(previous != null) builder.addAction(previous); if(play != null && !isPlaying) builder.addAction(play); if(pause != null && isPlaying) builder.addAction(pause); if(stop != null) builder.addAction(stop); if(next != null) builder.addAction(next); // Notifications of playing music can't be removed builder.setOngoing(isPlaying); builder.setSmallIcon(smallIcon); // Open the app when the notification is clicked String packageName = context.getPackageName(); Intent openApp = context.getPackageManager().getLaunchIntentForPackage(packageName); builder.setContentIntent(PendingIntent.getActivity(context, 0, openApp, 0)); if(!isPlaying) { // Remove notification Intent remove = new Intent(REMOVE_NOTIFICATION); remove.putExtra(PACKAGE_NAME, context.getApplicationInfo().packageName); builder.setDeleteIntent(PendingIntent.getBroadcast(context, 0, remove, PendingIntent.FLAG_UPDATE_CURRENT)); } // Finally show/update the notification NotificationManagerCompat.from(context).notify("MusicControl", 0, builder.build()); } public void hide() { NotificationManagerCompat.from(context).cancel("MusicControl", 0); } /** * Code taken from newer version of the support library located in PlaybackStateCompat.toKeyCode * Replace this to PlaybackStateCompat.toKeyCode when React Native updates the support library */ private int toKeyCode(long action) { if(action == PlaybackStateCompat.ACTION_PLAY) { return KeyEvent.KEYCODE_MEDIA_PLAY; } else if(action == PlaybackStateCompat.ACTION_PAUSE) { return KeyEvent.KEYCODE_MEDIA_PAUSE; } else if(action == PlaybackStateCompat.ACTION_SKIP_TO_NEXT) { return KeyEvent.KEYCODE_MEDIA_NEXT; } else if(action == PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) { return KeyEvent.KEYCODE_MEDIA_PREVIOUS; } else if(action == PlaybackStateCompat.ACTION_STOP) { return KeyEvent.KEYCODE_MEDIA_STOP; } else if(action == PlaybackStateCompat.ACTION_FAST_FORWARD) { return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; } else if(action == PlaybackStateCompat.ACTION_REWIND) { return KeyEvent.KEYCODE_MEDIA_REWIND; } else if(action == PlaybackStateCompat.ACTION_PLAY_PAUSE) { return KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; } return KeyEvent.KEYCODE_UNKNOWN; } private NotificationCompat.Action createAction(String iconName, String title, long mask, long action, NotificationCompat.Action oldAction) { if((mask & action) == 0) return null; // When this action is not enabled, return null if(oldAction != null) return oldAction; // If this action was already created, we won't create another instance // Finds the icon with the given name Resources r = context.getResources(); String packageName = context.getPackageName(); int icon = r.getIdentifier(iconName, "drawable", packageName); // Creates the intent based on the action int keyCode = toKeyCode(action); Intent intent = new Intent(MEDIA_BUTTON); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); intent.putExtra(PACKAGE_NAME, packageName); PendingIntent i = PendingIntent.getBroadcast(context, keyCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); return new NotificationCompat.Action(icon, title, i); } public static class NotificationService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public void onTaskRemoved(Intent rootIntent) { // Destroy the notification and sessions when the task is removed (closed, killed, etc) if(MusicControlModule.INSTANCE != null) { MusicControlModule.INSTANCE.destroy(); } stopSelf(); // Stop the service as we won't need it anymore } } }