From 8616c38ce1dcdc292fa756ed2e77f07e27aaa7cd Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Fri, 17 Mar 2017 16:31:54 +0100 Subject: [PATCH] Use old notifications for API < 21 --- .../gramophone/service/MultiPlayer.java | 2 +- .../gramophone/service/MusicService.java | 200 ++------------- .../notification/PlayingNotification.java | 15 ++ .../notification/PlayingNotificationImpl.java | 216 +++++++++++++++++ .../PlayingNotificationImpl21.java | 229 ++++++++++++++++++ .../{Playback => playback}/Playback.java | 2 +- 6 files changed, 478 insertions(+), 186 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java rename app/src/main/java/com/kabouzeid/gramophone/service/{Playback => playback}/Playback.java (93%) diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java b/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java index 2aea4bad..8a89b71f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java @@ -13,7 +13,7 @@ import android.util.Log; import android.widget.Toast; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.service.Playback.Playback; +import com.kabouzeid.gramophone.service.playback.Playback; import com.kabouzeid.gramophone.util.PreferenceUtil; /** diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index a728b5c3..387e6d55 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -1,7 +1,5 @@ package com.kabouzeid.gramophone.service; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.appwidget.AppWidgetManager; @@ -13,15 +11,12 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; import android.media.audiofx.AudioEffect; -import android.media.session.MediaSession; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -35,10 +30,6 @@ import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v7.app.NotificationCompat; -import android.support.v7.graphics.Palette; -import android.text.TextUtils; import android.widget.Toast; import com.bumptech.glide.BitmapRequestBuilder; @@ -51,15 +42,16 @@ import com.kabouzeid.gramophone.appwidgets.AppWidgetClassic; import com.kabouzeid.gramophone.appwidgets.AppWidgetSmall; import com.kabouzeid.gramophone.glide.BlurTransformation; import com.kabouzeid.gramophone.glide.SongGlideRequest; -import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; import com.kabouzeid.gramophone.helper.ShuffleHelper; import com.kabouzeid.gramophone.helper.StopWatch; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.provider.HistoryStore; import com.kabouzeid.gramophone.provider.MusicPlaybackQueueStore; import com.kabouzeid.gramophone.provider.SongPlayCountStore; -import com.kabouzeid.gramophone.service.Playback.Playback; -import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.service.notification.PlayingNotification; +import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl; +import com.kabouzeid.gramophone.service.notification.PlayingNotificationImpl21; +import com.kabouzeid.gramophone.service.playback.Playback; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; import com.kabouzeid.gramophone.util.Util; @@ -121,8 +113,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final int REPEAT_MODE_THIS = 2; public static final int SAVE_QUEUES = 0; - private static final int NOTIFY_MODE_FOREGROUND = 1; - private static final int NOTIFY_MODE_BACKGROUND = 0; private final IBinder musicBind = new MusicBinder(); @@ -140,9 +130,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private boolean queuesRestored; private boolean pausedByTransientLossOfFocus; private boolean receiversAndRemoteControlClientRegistered; - private MediaSessionCompat mediaSession; - private NotificationManager notificationManager; - private Notification notification; + private PlayingNotification playingNotification; private AudioManager audioManager; @SuppressWarnings("deprecation") private RemoteControlClient remoteControlClient; @@ -172,7 +160,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private boolean isServiceBound; private Handler uiThreadHandler; - private int mNotifyMode = NOTIFY_MODE_BACKGROUND; private static String getTrackUri(@NonNull Song song) { return MusicUtil.getSongFileUri(song.id).toString(); @@ -204,8 +191,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - setupMediaSession(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + playingNotification = new PlayingNotificationImpl21(); + } else { + playingNotification = new PlayingNotificationImpl(); + } + playingNotification.init(this); mediaStoreObserver = new MediaStoreObserver(playerHandler); throttledSeekHandler = new ThrottledSeekHandler(playerHandler); @@ -296,9 +287,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP getContentResolver().unregisterContentObserver(mediaStoreObserver); PreferenceUtil.getInstance(this).unregisterOnSharedPreferenceChangedListener(this); wakeLock.release(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mediaSession.release(); - } sendBroadcast(new Intent("com.kabouzeid.gramophone.PHONOGRAPH_MUSIC_SERVICE_DESTROYED")); } @@ -413,8 +401,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private int quit() { unregisterReceiversAndRemoteControlClient(); pause(); - notificationManager.cancelAll(); - killNotification(); + playingNotification.stop(); if (isServiceBound) { return START_STICKY; @@ -974,7 +961,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP switch (what) { case PLAY_STATE_CHANGED: final boolean isPlaying = isPlaying(); - buildNotification(); + playingNotification.update(); //noinspection deprecation remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED); if (!isPlaying && getSongProgressMillis() > 0) { @@ -983,7 +970,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP songPlayCountHelper.notifyPlayStateChanged(isPlaying); break; case META_CHANGED: - buildNotification(); + playingNotification.update(); updateRemoteControlClient(); savePosition(); savePositionInTrack(); @@ -999,7 +986,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP if (playingQueue.size() > 0) { prepareNext(); } else { - notificationManager.cancelAll(); + quit(); } break; } @@ -1034,7 +1021,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP updateRemoteControlClient(); break; case PreferenceUtil.COLORED_NOTIFICATION: - buildNotification(); + playingNotification.update(); break; } } @@ -1050,161 +1037,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP playerHandler.sendEmptyMessage(TRACK_ENDED); } - private void setupMediaSession() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mediaSession = new MediaSessionCompat(this, "Phonograph"); - mediaSession.setCallback(new MediaSessionCompat.Callback() { - @Override - public void onPlay() { - play(); - } - - @Override - public void onPause() { - pause(); - } - - @Override - public void onSkipToNext() { - playNextSong(true); - } - - @Override - public void onSkipToPrevious() { - back(true); - } - - @Override - public void onStop() { - stop(); - } - - }); - - PendingIntent pi = PendingIntent.getBroadcast(this, 0, - new Intent(this, MediaButtonIntentReceiver.class), - PendingIntent.FLAG_UPDATE_CURRENT); - mediaSession.setMediaButtonReceiver(pi); - - mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS - | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); - } - } - - private synchronized void buildNotification() { - final Song song = getCurrentSong(); - - final String albumName = song.albumName; - final String artistName = song.artistName; - final boolean isPlaying = isPlaying(); - final String text = TextUtils.isEmpty(albumName) - ? artistName : artistName + " - " + albumName; - - final int playButtonResId = isPlaying - ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; - - Intent action = new Intent(this, MainActivity.class); - action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - final PendingIntent clickIntent = PendingIntent.getActivity(this, 0, action, 0); - - final ComponentName serviceName = new ComponentName(this, MusicService.class); - Intent intent = new Intent(MusicService.ACTION_QUIT); - intent.setComponent(serviceName); - final PendingIntent deleteIntent = PendingIntent.getService(this, 0, intent, 0); - - final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) - .checkIgnoreMediaStore(MusicService.this) - .generatePalette(this).build(); - - final int bigNotificationImageSize = getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); - runOnUiThread(new Runnable() { - @Override - public void run() { - request.into(new SimpleTarget(bigNotificationImageSize, bigNotificationImageSize) { - @Override - public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { - Palette palette = resource.getPalette(); - update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT))); - } - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - update(null, Color.TRANSPARENT); - } - - void update(Bitmap bitmap, int color) { - if (bitmap == null) bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_album_art); - NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId, - getString(R.string.action_play_pause), - retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); - NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp, - getString(R.string.action_previous), - retrievePlaybackAction(ACTION_REWIND)); - NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp, - getString(R.string.action_next), - retrievePlaybackAction(ACTION_SKIP)); - NotificationCompat.Builder builder = (NotificationCompat.Builder) new NotificationCompat.Builder(MusicService.this) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(bitmap) - .setContentIntent(clickIntent) - .setDeleteIntent(deleteIntent) - .setContentTitle(song.title) - .setContentText(text) - .setOngoing(isPlaying) - .setShowWhen(false) - .addAction(previousAction) - .addAction(playPauseAction) - .addAction(nextAction); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - if (PreferenceUtil.getInstance(MusicService.this).coloredNotification()) builder.setColor(color); - } - - notification = builder.build(); - updateNotification(notification); - } - - }); - - } - }); - } - - private void updateNotification(Notification notification) { - int newNotifyMode; - if (isPlaying()) { - newNotifyMode = NOTIFY_MODE_FOREGROUND; - } else { - newNotifyMode = NOTIFY_MODE_BACKGROUND; - } - - if (mNotifyMode != newNotifyMode && mNotifyMode == NOTIFY_MODE_FOREGROUND) { - stopForeground(false); - } - - if (newNotifyMode == NOTIFY_MODE_FOREGROUND) { - startForeground(1, notification); - } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) { - notificationManager.notify(1, notification); - } - - mNotifyMode = newNotifyMode; - } - - private PendingIntent retrievePlaybackAction(final String action) { - final ComponentName serviceName = new ComponentName(this, MusicService.class); - Intent intent = new Intent(action); - intent.setComponent(serviceName); - - return PendingIntent.getService(this, 0, intent, 0); - } - - public synchronized void killNotification() { - this.stopForeground(true); - notificationManager.cancelAll(); - } - private static final class PlaybackHandler extends Handler { @NonNull private final WeakReference mService; diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java new file mode 100644 index 00000000..43558726 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java @@ -0,0 +1,15 @@ +package com.kabouzeid.gramophone.service.notification; + +import com.kabouzeid.gramophone.service.MusicService; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ + +public interface PlayingNotification { + void init(MusicService service); + + void update(); + + void stop(); +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java new file mode 100644 index 00000000..5ddbc2aa --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java @@ -0,0 +1,216 @@ +package com.kabouzeid.gramophone.service.notification; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; +import com.kabouzeid.appthemehelper.util.ColorUtil; +import com.kabouzeid.appthemehelper.util.MaterialValueHelper; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.glide.SongGlideRequest; +import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.service.MusicService; +import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.util.PhonographColorUtil; +import com.kabouzeid.gramophone.util.PreferenceUtil; +import com.kabouzeid.gramophone.util.Util; + +public class PlayingNotificationImpl implements PlayingNotification { + + private MusicService service; + + private Target target; + + private boolean stopped; + + @Override + public synchronized void init(MusicService service) { + this.service = service; + } + + @Override + public synchronized void update() { + stopped = false; + + final Song song = service.getCurrentSong(); + + final boolean isPlaying = service.isPlaying(); + + final RemoteViews notificationLayout = new RemoteViews(service.getPackageName(), R.layout.notification); + final RemoteViews notificationLayoutBig = new RemoteViews(service.getPackageName(), R.layout.notification_big); + + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + notificationLayout.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + notificationLayout.setViewVisibility(R.id.media_titles, View.VISIBLE); + notificationLayout.setTextViewText(R.id.title, song.title); + notificationLayout.setTextViewText(R.id.text, song.artistName); + } + + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName) && TextUtils.isEmpty(song.albumName)) { + notificationLayoutBig.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + notificationLayoutBig.setViewVisibility(R.id.media_titles, View.VISIBLE); + notificationLayoutBig.setTextViewText(R.id.title, song.title); + notificationLayoutBig.setTextViewText(R.id.text, song.artistName); + notificationLayoutBig.setTextViewText(R.id.text2, song.albumName); + } + + linkButtons(notificationLayout, notificationLayoutBig); + + Intent action = new Intent(service, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent openAppPendingIntent = PendingIntent.getActivity(service, 0, action, 0); + + final Notification notification = new NotificationCompat.Builder(service) + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(openAppPendingIntent) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContent(notificationLayout) + .build(); + + notification.bigContentView = notificationLayoutBig; + + final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .into(new SimpleTarget(bigNotificationImageSize, bigNotificationImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { + update(resource.getBitmap(), PhonographColorUtil.getColor(resource.getPalette(), Color.TRANSPARENT)); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, Color.TRANSPARENT); + } + + private void update(@Nullable Bitmap bitmap, int bgColor) { + if (bitmap != null) { + notificationLayout.setImageViewBitmap(R.id.image, bitmap); + notificationLayoutBig.setImageViewBitmap(R.id.image, bitmap); + } else { + notificationLayout.setImageViewResource(R.id.image, R.drawable.default_album_art); + notificationLayoutBig.setImageViewResource(R.id.image, R.drawable.default_album_art); + } + + if (!PreferenceUtil.getInstance(service).coloredNotification()) { + bgColor = Color.TRANSPARENT; + } + setBackgroundColor(bgColor); + setNotificationContent(bgColor == Color.TRANSPARENT ? Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP : ColorUtil.isColorLight(bgColor)); + + if (stopped) + return; // notification has been stopped before loading was finished + service.startForeground(1, notification); + } + + private void setBackgroundColor(int color) { + notificationLayout.setInt(R.id.root, "setBackgroundColor", color); + notificationLayoutBig.setInt(R.id.root, "setBackgroundColor", color); + } + + private void setNotificationContent(boolean dark) { + int primary = MaterialValueHelper.getPrimaryTextColor(service, dark); + int secondary = MaterialValueHelper.getSecondaryTextColor(service, dark); + + Bitmap prev = createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary), 1.5f); + Bitmap next = createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, primary), 1.5f); + Bitmap playPause = createBitmap(Util.getTintedVectorDrawable(service, isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp, primary), 1.5f); + Bitmap close = createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_close_white_24dp, secondary), 1f); + + notificationLayout.setTextColor(R.id.title, primary); + notificationLayout.setTextColor(R.id.text, secondary); + notificationLayout.setImageViewBitmap(R.id.action_prev, prev); + notificationLayout.setImageViewBitmap(R.id.action_next, next); + notificationLayout.setImageViewBitmap(R.id.action_play_pause, playPause); + + notificationLayoutBig.setTextColor(R.id.title, primary); + notificationLayoutBig.setTextColor(R.id.text, secondary); + notificationLayoutBig.setTextColor(R.id.text2, secondary); + notificationLayoutBig.setImageViewBitmap(R.id.action_prev, prev); + notificationLayoutBig.setImageViewBitmap(R.id.action_next, next); + notificationLayoutBig.setImageViewBitmap(R.id.action_play_pause, playPause); + notificationLayoutBig.setImageViewBitmap(R.id.action_quit, close); + } + }); + } + }); + } + + @Override + public synchronized void stop() { + stopped = true; + service.stopForeground(true); + } + + private void linkButtons(final RemoteViews notificationLayout, final RemoteViews notificationLayoutBig) { + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(service, MusicService.class); + + // Previous track + pendingIntent = buildPendingIntent(service, MusicService.ACTION_REWIND, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(service, MusicService.ACTION_TOGGLE_PAUSE, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(service, MusicService.ACTION_SKIP, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_next, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_next, pendingIntent); + + // Quit + pendingIntent = buildPendingIntent(service, MusicService.ACTION_QUIT, serviceName); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_quit, pendingIntent); + } + + private PendingIntent buildPendingIntent(Context context, final String action, final ComponentName serviceName) { + Intent intent = new Intent(action); + intent.setComponent(serviceName); + return PendingIntent.getService(context, 0, intent, 0); + } + + private static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java new file mode 100644 index 00000000..435e1ade --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl21.java @@ -0,0 +1,229 @@ +package com.kabouzeid.gramophone.service.notification; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.media.session.MediaSession; +import android.os.Build; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v7.app.NotificationCompat; +import android.support.v7.graphics.Palette; +import android.text.TextUtils; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.glide.SongGlideRequest; +import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.service.MediaButtonIntentReceiver; +import com.kabouzeid.gramophone.service.MusicService; +import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.util.PreferenceUtil; + +import hugo.weaving.DebugLog; + +import static android.content.Context.NOTIFICATION_SERVICE; +import static com.kabouzeid.gramophone.service.MusicService.ACTION_REWIND; +import static com.kabouzeid.gramophone.service.MusicService.ACTION_SKIP; +import static com.kabouzeid.gramophone.service.MusicService.ACTION_TOGGLE_PAUSE; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ + +public class PlayingNotificationImpl21 implements PlayingNotification { + private static final int NOTIFY_MODE_FOREGROUND = 1; + private static final int NOTIFY_MODE_BACKGROUND = 0; + + private MusicService service; + + private MediaSessionCompat mediaSession; + private NotificationManager notificationManager; + + private int notifyMode = NOTIFY_MODE_BACKGROUND; + + private boolean stopped; + + @Override + public synchronized void init(MusicService service) { + this.service = service; + notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE); + setupMediaSession(); + } + + @DebugLog + private void setupMediaSession() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mediaSession = new MediaSessionCompat(service, "Phonograph"); + mediaSession.setCallback(new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + service.play(); + } + + @Override + public void onPause() { + service.pause(); + } + + @Override + public void onSkipToNext() { + service.playNextSong(true); + } + + @Override + public void onSkipToPrevious() { + service.back(true); + } + + @Override + public void onStop() { + service.stop(); + } + + @Override + public void onSeekTo(long pos) { + service.seek((int) pos); + } + }); + + PendingIntent pi = PendingIntent.getBroadcast(service, 0, + new Intent(service, MediaButtonIntentReceiver.class), + PendingIntent.FLAG_UPDATE_CURRENT); + mediaSession.setMediaButtonReceiver(pi); + + mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS + | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); + } + } + + @Override + public synchronized void update() { + stopped = false; + + final Song song = service.getCurrentSong(); + + final String albumName = song.albumName; + final String artistName = song.artistName; + final boolean isPlaying = service.isPlaying(); + final String text = TextUtils.isEmpty(albumName) + ? artistName : artistName + " - " + albumName; + + final int playButtonResId = isPlaying + ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + + Intent action = new Intent(service, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0); + + final ComponentName serviceName = new ComponentName(service, MusicService.class); + Intent intent = new Intent(MusicService.ACTION_QUIT); + intent.setComponent(serviceName); + final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0); + + final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .into(new SimpleTarget(bigNotificationImageSize, bigNotificationImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { + Palette palette = resource.getPalette(); + update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT))); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + update(null, Color.TRANSPARENT); + } + + void update(Bitmap bitmap, int color) { + if (bitmap == null) + bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art); + NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId, + service.getString(R.string.action_play_pause), + retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); + NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp, + service.getString(R.string.action_previous), + retrievePlaybackAction(ACTION_REWIND)); + NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp, + service.getString(R.string.action_next), + retrievePlaybackAction(ACTION_SKIP)); + NotificationCompat.Builder builder = (NotificationCompat.Builder) new NotificationCompat.Builder(service) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(bitmap) + .setContentIntent(clickIntent) + .setDeleteIntent(deleteIntent) + .setContentTitle(song.title) + .setContentText(text) + .setOngoing(isPlaying) + .setShowWhen(false) + .addAction(previousAction) + .addAction(playPauseAction) + .addAction(nextAction); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setStyle(new NotificationCompat.MediaStyle().setMediaSession(mediaSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + if (PreferenceUtil.getInstance(service).coloredNotification()) + builder.setColor(color); + } + + if (stopped) + return; // notification has been stopped before loading was finished + updateNotifyModeAndPostNotification(builder.build()); + } + + }); + + } + }); + } + + private PendingIntent retrievePlaybackAction(final String action) { + final ComponentName serviceName = new ComponentName(service, MusicService.class); + Intent intent = new Intent(action); + intent.setComponent(serviceName); + + return PendingIntent.getService(service, 0, intent, 0); + } + + private void updateNotifyModeAndPostNotification(Notification notification) { + int newNotifyMode; + if (service.isPlaying()) { + newNotifyMode = NOTIFY_MODE_FOREGROUND; + } else { + newNotifyMode = NOTIFY_MODE_BACKGROUND; + } + + if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) { + service.stopForeground(false); + } + + if (newNotifyMode == NOTIFY_MODE_FOREGROUND) { + service.startForeground(1, notification); + } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) { + notificationManager.notify(1, notification); + } + + notifyMode = newNotifyMode; + } + + @Override + public synchronized void stop() { + stopped = true; + service.stopForeground(true); + mediaSession.release(); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/Playback/Playback.java b/app/src/main/java/com/kabouzeid/gramophone/service/playback/Playback.java similarity index 93% rename from app/src/main/java/com/kabouzeid/gramophone/service/Playback/Playback.java rename to app/src/main/java/com/kabouzeid/gramophone/service/playback/Playback.java index dc2783b4..ded13670 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/Playback/Playback.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/playback/Playback.java @@ -1,4 +1,4 @@ -package com.kabouzeid.gramophone.service.Playback; +package com.kabouzeid.gramophone.service.playback; import android.support.annotation.Nullable;