From fde73fd6b4627ac47c774e35b487b2626a4665da Mon Sep 17 00:00:00 2001 From: doompadee <31240866+doompadee@users.noreply.github.com> Date: Sun, 7 Jan 2018 15:01:12 +0100 Subject: [PATCH 1/2] Add sleep timer countdown and action to playing notification Signed-off-by: doompadee <31240866+doompadee@users.noreply.github.com> --- app/src/main/AndroidManifest.xml | 7 +- .../gramophone/dialogs/SleepTimerDialog.java | 10 ++- .../gramophone/service/MusicService.java | 11 +++ .../PlayingNotificationImpl24.java | 88 ++++++++++++++++--- .../ui/activities/SleepTimerActivity.java | 45 ++++++++++ .../gramophone/util/PreferenceUtil.java | 6 +- app/src/main/res/values/styles.xml | 9 ++ 7 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/ui/activities/SleepTimerActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 44e43518f..cd346bcd6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -121,7 +121,12 @@ android:name=".appshortcuts.AppShortcutLauncherActivity" android:launchMode="singleInstance" android:theme="@android:style/Theme.Translucent.NoTitleBar" /> - + + diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java index b7d452c55..e71d6a0c5 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java @@ -46,6 +46,9 @@ public class SleepTimerDialog extends DialogFragment { public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); timerUpdater.cancel(); + if (getActivity() instanceof DismissListener) { + ((DismissListener) getActivity()).onDismissed(); + } } @NonNull @@ -73,7 +76,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { PreferenceUtil.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime); AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi); - + getActivity().sendBroadcast(new Intent(MusicService.SLEEP_TIMER_CHANGED)); Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_set, minutes), Toast.LENGTH_SHORT).show(); }) .onNeutral((dialog, which) -> { @@ -85,6 +88,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); am.cancel(previous); previous.cancel(); + getActivity().sendBroadcast(new Intent(MusicService.SLEEP_TIMER_CHANGED)); Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show(); } }) @@ -172,4 +176,8 @@ public void onFinish() { materialDialog.setActionButton(DialogAction.NEUTRAL, null); } } + + public interface DismissListener { + void onDismissed(); + } } \ No newline at end of file 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 84dc7e116..a727b1fd6 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -98,6 +98,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final String REPEAT_MODE_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".repeatmodechanged"; public static final String SHUFFLE_MODE_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".shufflemodechanged"; public static final String MEDIA_STORE_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".mediastorechanged"; + public static final String SLEEP_TIMER_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".sleeptimerchanged"; public static final String SAVED_POSITION = "POSITION"; public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; @@ -167,6 +168,14 @@ public void onReceive(Context context, @NonNull Intent intent) { } } }; + + private final BroadcastReceiver sleepTimerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, @NonNull Intent intent) { + updateNotification(); + } + }; + private ContentObserver mediaStoreObserver; private boolean notHandledMetaChangedForCurrentTrack; @@ -201,6 +210,7 @@ public void onCreate() { uiThreadHandler = new Handler(); registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + registerReceiver(sleepTimerReceiver, new IntentFilter(SLEEP_TIMER_CHANGED)); initNotification(); @@ -348,6 +358,7 @@ public int onStartCommand(@Nullable Intent intent, int flags, int startId) { @Override public void onDestroy() { unregisterReceiver(widgetIntentReceiver); + unregisterReceiver(sleepTimerReceiver); if (becomingNoisyReceiverRegistered) { unregisterReceiver(becomingNoisyReceiver); becomingNoisyReceiverRegistered = false; diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java index 4d197f273..60df88c69 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java @@ -1,5 +1,6 @@ package com.kabouzeid.gramophone.service.notification; +import android.app.Notification; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; @@ -8,6 +9,8 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.SystemClock; +import android.support.annotation.RequiresApi; import android.support.v4.app.NotificationCompat; import android.support.v4.media.app.NotificationCompat.MediaStyle; import android.support.v7.graphics.Palette; @@ -17,6 +20,7 @@ import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.ui.activities.SleepTimerActivity; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; import com.kabouzeid.gramophone.model.Song; @@ -48,11 +52,8 @@ public synchronized void update() { 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 PendingIntent deleteIntent = PendingIntent.getService(service, 1, stopPlayingIntent(), 0); + PendingIntent sleepTimerIntent = PendingIntent.getActivity(service, 0, new Intent(service, SleepTimerActivity.class), 0); final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); service.runOnUiThread(() -> SongGlideRequest.Builder.from(Glide.with(service), song) @@ -73,6 +74,64 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) { void update(Bitmap bitmap, int color) { if (bitmap == null) bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art); + + Notification notification; + + if (isSleepTimerActive()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notification = getChronoNotification(bitmap, color); + } else { + notification = getLegacyNotification(bitmap, color); + } + } else { + notification = getLegacyNotification(bitmap, color); + } + + if (stopped) + return; // notification has been stopped before loading was finished + updateNotifyModeAndPostNotification(notification); + } + + @RequiresApi(26) + Notification getChronoNotification(Bitmap bitmap, int color) { + long time = PreferenceUtil.getInstance(service).getNextSleepTimerElapsedRealTime(); + Notification.Action playPauseAction = new Notification.Action(playButtonResId, + service.getString(R.string.action_play_pause), + retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); + Notification.Action previousAction = new Notification.Action(R.drawable.ic_skip_previous_white_24dp, + service.getString(R.string.action_previous), + retrievePlaybackAction(ACTION_REWIND)); + Notification.Action nextAction = new Notification.Action(R.drawable.ic_skip_next_white_24dp, + service.getString(R.string.action_next), + retrievePlaybackAction(ACTION_SKIP)); + Notification.Action sleepTimerAction = new Notification.Action(R.drawable.ic_timer_white_24dp, + service.getString(R.string.action_sleep_timer), + sleepTimerIntent); + Notification.Builder builder = new Notification.Builder(service, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(bitmap) + .setContentIntent(clickIntent) + .setDeleteIntent(deleteIntent) + .setContentTitle(song.title) + .setContentText(text) + .setOngoing(isPlaying) + .setShowWhen(isPlaying) + .setUsesChronometer(true) + .setChronometerCountDown(true) + .setWhen(System.currentTimeMillis() + (time - SystemClock.elapsedRealtime())) + .addAction(previousAction) + .addAction(playPauseAction) + .addAction(nextAction) + .addAction(sleepTimerAction) + .setStyle(new Notification.MediaStyle().setMediaSession((android.media.session.MediaSession.Token)service.getMediaSession().getSessionToken().getToken()).setShowActionsInCompactView(0, 1, 2)); + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).coloredNotification()) { + builder.setColor(color); + } + return builder.build(); + } + + Notification getLegacyNotification(Bitmap bitmap, int color) { NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId, service.getString(R.string.action_play_pause), retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); @@ -82,6 +141,9 @@ void update(Bitmap bitmap, int color) { NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp, service.getString(R.string.action_next), retrievePlaybackAction(ACTION_SKIP)); + NotificationCompat.Action sleepTimerAction = new NotificationCompat.Action(R.drawable.ic_timer_white_24dp, + service.getString(R.string.action_sleep_timer), + sleepTimerIntent); NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setLargeIcon(bitmap) @@ -93,7 +155,8 @@ void update(Bitmap bitmap, int color) { .setShowWhen(false) .addAction(previousAction) .addAction(playPauseAction) - .addAction(nextAction); + .addAction(nextAction) + .addAction(sleepTimerAction); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setStyle(new MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2)) @@ -101,10 +164,7 @@ void update(Bitmap bitmap, int color) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).coloredNotification()) builder.setColor(color); } - - if (stopped) - return; // notification has been stopped before loading was finished - updateNotifyModeAndPostNotification(builder.build()); + return builder.build(); } })); } @@ -115,4 +175,12 @@ private PendingIntent retrievePlaybackAction(final String action) { intent.setComponent(serviceName); return PendingIntent.getService(service, 0, intent, 0); } + + private boolean isSleepTimerActive() { + return PendingIntent.getService(service, 0, stopPlayingIntent(), PendingIntent.FLAG_NO_CREATE) != null; + } + + private Intent stopPlayingIntent() { + return new Intent(service, MusicService.class).setAction(MusicService.ACTION_QUIT); + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SleepTimerActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SleepTimerActivity.java new file mode 100644 index 000000000..4e952cd62 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SleepTimerActivity.java @@ -0,0 +1,45 @@ +package com.kabouzeid.gramophone.ui.activities; + +import android.os.Bundle; + +import android.support.annotation.Nullable; + +import com.afollestad.materialdialogs.Theme; +import com.kabouzeid.appthemehelper.ATHActivity; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.dialogs.SleepTimerDialog; +import com.kabouzeid.gramophone.util.PreferenceUtil; + + +public class SleepTimerActivity extends ATHActivity implements SleepTimerDialog.DismissListener { + + @Override + public void onDismissed() { + finish(); + } + + @Override + protected int getThemeRes() { + // the SleepTimerDialog theme is chosen by the primary text color of the context. We + // therefore have to match the overall theme + return (getDialogTheme() == Theme.LIGHT) + ? R.style.Activity_Light_Dialog + : R.style.Activity_Dark_Dialog; + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + new SleepTimerDialog().show(getSupportFragmentManager(), "fragment_dialog"); + } + + private Theme getDialogTheme() { + switch (PreferenceUtil.getInstance(this).getTheme()) { + case "dark": + case "black": + return Theme.DARK; + default: + return Theme.LIGHT; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java index b1d6c2c10..3561d4f85 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java @@ -106,7 +106,11 @@ public void unregisterOnSharedPreferenceChangedListener(SharedPreferences.OnShar } public int getGeneralTheme() { - return getThemeResFromPrefValue(mPreferences.getString(GENERAL_THEME, "")); + return getThemeResFromPrefValue(getTheme()); + } + + public String getTheme() { + return mPreferences.getString(GENERAL_THEME, ""); } @StyleRes diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7252a7b93..87932db55 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -37,4 +37,13 @@ toolbar + + + From e05eed47cde43cedbe02465a9892ee4cd3cd20af Mon Sep 17 00:00:00 2001 From: doompadee <31240866+doompadee@users.noreply.github.com> Date: Thu, 11 Jan 2018 17:13:35 +0100 Subject: [PATCH 2/2] Provide support for legacy notification, cleanup --- .../gramophone/dialogs/SleepTimerDialog.java | 16 +-- .../notification/PlayingNotification.java | 23 ++++ .../notification/PlayingNotificationImpl.java | 99 +++++++++++----- .../PlayingNotificationImpl24.java | 109 ++++-------------- .../gramophone/util/SleepTimerUtil.java | 46 ++++++++ app/src/main/res/layout/notification_big.xml | 70 +++++++---- 6 files changed, 208 insertions(+), 155 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/util/SleepTimerUtil.java diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java index e71d6a0c5..35e1b9c62 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java @@ -24,6 +24,7 @@ import com.kabouzeid.gramophone.ui.activities.PurchaseActivity; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; +import com.kabouzeid.gramophone.util.SleepTimerUtil; import com.triggertrap.seekarc.SeekArc; import butterknife.BindView; @@ -70,7 +71,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { final int minutes = seekArcProgress; - PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pi = SleepTimerUtil.createTimer(getActivity()); final long nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + minutes * 60 * 1000; PreferenceUtil.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime); @@ -83,7 +84,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { if (getActivity() == null) { return; } - final PendingIntent previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE); + final PendingIntent previous = SleepTimerUtil.getCurrentTimer(getActivity()); if (previous != null) { AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); am.cancel(previous); @@ -93,7 +94,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { } }) .showListener(dialog -> { - if (makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) != null) { + if (SleepTimerUtil.isTimerRunning(getActivity())) { timerUpdater.start(); } }) @@ -152,15 +153,6 @@ private void updateTimeDisplayTime() { timerDisplay.setText(seekArcProgress + " min"); } - private PendingIntent makeTimerPendingIntent(int flag) { - return PendingIntent.getService(getActivity(), 0, makeTimerIntent(), flag); - } - - private Intent makeTimerIntent() { - return new Intent(getActivity(), MusicService.class) - .setAction(MusicService.ACTION_QUIT); - } - private class TimerUpdater extends CountDownTimer { public TimerUpdater() { super(PreferenceUtil.getInstance(getActivity()).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(), 1000); 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 index 2969a4e2d..eaff43916 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java @@ -3,11 +3,16 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; import android.os.Build; import android.support.annotation.RequiresApi; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.service.MusicService; +import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.ui.activities.SleepTimerActivity; +import com.kabouzeid.gramophone.util.SleepTimerUtil; import static android.content.Context.NOTIFICATION_SERVICE; @@ -41,6 +46,24 @@ public synchronized void stop() { notificationManager.cancel(NOTIFICATION_ID); } + PendingIntent clickAction() { + Intent intent = new Intent(service, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + return PendingIntent.getActivity(service, 0, intent, 0); + } + + PendingIntent playbackAction(final String action) { + Intent intent = new Intent(service, MusicService.class).setAction(action); + return PendingIntent.getService(service, 0, intent, 0); + } + + PendingIntent deleteAction() { + return PendingIntent.getService(service, 0, SleepTimerUtil.getTimerAction(service), 0); + } + + PendingIntent sleepTimerAction() { + return PendingIntent.getActivity(service, 0, new Intent(service, SleepTimerActivity.class), 0); + } + void updateNotifyModeAndPostNotification(Notification notification) { int newNotifyMode; if (service.isPlaying()) { 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 index 357a91061..41a984d82 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java @@ -2,13 +2,12 @@ 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.CountDownTimer; +import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; @@ -26,15 +25,20 @@ 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.SleepTimerUtil; import com.kabouzeid.gramophone.util.Util; +import java.util.concurrent.TimeUnit; + public class PlayingNotificationImpl extends PlayingNotification { private Target target; + private CountDownTimer countdown; + private long timerStopTime; + @Override public synchronized void update() { stopped = false; @@ -46,6 +50,38 @@ public synchronized void update() { final RemoteViews notificationLayout = new RemoteViews(service.getPackageName(), R.layout.notification); final RemoteViews notificationLayoutBig = new RemoteViews(service.getPackageName(), R.layout.notification_big); + if (SleepTimerUtil.isTimerRunning(service)) { + long stopTime = PreferenceUtil.getInstance(service).getNextSleepTimerElapsedRealTime(); + + if (countdown != null && stopTime != timerStopTime) { + countdown.cancel(); + countdown = null; + } + + if (countdown == null) { + timerStopTime = stopTime; + countdown = new CountDownTimer(timerStopTime - SystemClock.elapsedRealtime(), 60 * 1000) { + @Override + public void onTick(long millisUntilFinished) { + update(); + } + + @Override + public void onFinish() { + countdown = null; + } + }; + countdown.start(); + } + + notificationLayoutBig.setViewVisibility(R.id.sleep_timer, View.VISIBLE); + } else if (countdown != null) { + countdown.cancel(); + countdown = null; + + notificationLayoutBig.setViewVisibility(R.id.sleep_timer, View.GONE); + } + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { notificationLayout.setViewVisibility(R.id.media_titles, View.INVISIBLE); } else { @@ -65,15 +101,10 @@ public synchronized void update() { linkButtons(notificationLayout, notificationLayoutBig); - 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 PendingIntent deleteIntent = buildPendingIntent(service, MusicService.ACTION_QUIT, null); - final Notification notification = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) - .setContentIntent(clickIntent) - .setDeleteIntent(deleteIntent) + .setContentIntent(clickAction()) + .setDeleteIntent(deleteAction()) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setPriority(NotificationCompat.PRIORITY_MAX) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -136,6 +167,7 @@ private void setNotificationContent(boolean 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 sleepTimer = createBitmap(Util.getTintedVectorDrawable(service, R.drawable.ic_timer_white_24dp, primary), 1.5f); notificationLayout.setTextColor(R.id.title, primary); notificationLayout.setTextColor(R.id.text, secondary); @@ -146,9 +178,21 @@ private void setNotificationContent(boolean dark) { notificationLayoutBig.setTextColor(R.id.title, primary); notificationLayoutBig.setTextColor(R.id.text, secondary); notificationLayoutBig.setTextColor(R.id.text2, secondary); + notificationLayoutBig.setTextColor(R.id.sleep_timer, primary); 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_sleep_timer, sleepTimer); + + if (SleepTimerUtil.isTimerRunning(service)) { + long millis = PreferenceUtil.getInstance(service).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(); + long minutes = TimeUnit.MINUTES.convert(millis, TimeUnit.MILLISECONDS) + 1; + String text = String.format("%d min left", minutes) ; + notificationLayoutBig.setTextViewText(R.id.sleep_timer, text); + notificationLayoutBig.setViewVisibility(R.id.sleep_timer,View.VISIBLE); + } else { + notificationLayoutBig.setViewVisibility(R.id.sleep_timer, View.GONE); + } } }); } @@ -156,30 +200,21 @@ private void setNotificationContent(boolean dark) { } private void linkButtons(final RemoteViews notificationLayout, final RemoteViews notificationLayoutBig) { - PendingIntent pendingIntent; - - final ComponentName serviceName = new ComponentName(service, MusicService.class); + PendingIntent previous = playbackAction(MusicService.ACTION_REWIND); + notificationLayout.setOnClickPendingIntent(R.id.action_prev, previous); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, previous); - // Previous track - pendingIntent = buildPendingIntent(service, MusicService.ACTION_REWIND, serviceName); - notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent); - notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + PendingIntent playPause = playbackAction(MusicService.ACTION_TOGGLE_PAUSE); + notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, playPause); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_play_pause, playPause); - // 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); - } + PendingIntent next = playbackAction(MusicService.ACTION_SKIP); + notificationLayout.setOnClickPendingIntent(R.id.action_next, next); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_next, next); - 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); + PendingIntent sleepTimer = sleepTimerAction(); + notificationLayout.setOnClickPendingIntent(R.id.action_sleep_timer, sleepTimer); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_sleep_timer, sleepTimer); } private static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java index 60df88c69..ccf7ebac8 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java @@ -2,17 +2,14 @@ import android.app.Notification; 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.os.Build; import android.os.SystemClock; +import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; -import android.support.v4.app.NotificationCompat; -import android.support.v4.media.app.NotificationCompat.MediaStyle; import android.support.v7.graphics.Palette; import android.text.TextUtils; @@ -20,18 +17,17 @@ import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.ui.activities.SleepTimerActivity; 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.PreferenceUtil; +import com.kabouzeid.gramophone.util.SleepTimerUtil; 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; +@RequiresApi(24) public class PlayingNotificationImpl24 extends PlayingNotification { @Override @@ -49,11 +45,9 @@ public synchronized void update() { 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 PendingIntent deleteIntent = PendingIntent.getService(service, 1, stopPlayingIntent(), 0); - PendingIntent sleepTimerIntent = PendingIntent.getActivity(service, 0, new Intent(service, SleepTimerActivity.class), 0); + final PendingIntent clickIntent = clickAction(); + final PendingIntent deleteIntent = deleteAction(); + PendingIntent sleepTimerIntent = sleepTimerAction(); final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); service.runOnUiThread(() -> SongGlideRequest.Builder.from(Glide.with(service), song) @@ -74,40 +68,21 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) { void update(Bitmap bitmap, int color) { if (bitmap == null) bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art); - - Notification notification; - - if (isSleepTimerActive()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notification = getChronoNotification(bitmap, color); - } else { - notification = getLegacyNotification(bitmap, color); - } - } else { - notification = getLegacyNotification(bitmap, color); - } - - if (stopped) - return; // notification has been stopped before loading was finished - updateNotifyModeAndPostNotification(notification); - } - - @RequiresApi(26) - Notification getChronoNotification(Bitmap bitmap, int color) { long time = PreferenceUtil.getInstance(service).getNextSleepTimerElapsedRealTime(); + boolean sleepTimer = SleepTimerUtil.isTimerRunning(service); Notification.Action playPauseAction = new Notification.Action(playButtonResId, service.getString(R.string.action_play_pause), - retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); + playbackAction(ACTION_TOGGLE_PAUSE)); Notification.Action previousAction = new Notification.Action(R.drawable.ic_skip_previous_white_24dp, service.getString(R.string.action_previous), - retrievePlaybackAction(ACTION_REWIND)); + playbackAction(ACTION_REWIND)); Notification.Action nextAction = new Notification.Action(R.drawable.ic_skip_next_white_24dp, service.getString(R.string.action_next), - retrievePlaybackAction(ACTION_SKIP)); + playbackAction(ACTION_SKIP)); Notification.Action sleepTimerAction = new Notification.Action(R.drawable.ic_timer_white_24dp, service.getString(R.string.action_sleep_timer), sleepTimerIntent); - Notification.Builder builder = new Notification.Builder(service, NOTIFICATION_CHANNEL_ID) + Notification.Builder builder = builder() .setSmallIcon(R.drawable.ic_notification) .setLargeIcon(bitmap) .setContentIntent(clickIntent) @@ -115,10 +90,10 @@ Notification getChronoNotification(Bitmap bitmap, int color) { .setContentTitle(song.title) .setContentText(text) .setOngoing(isPlaying) - .setShowWhen(isPlaying) + .setShowWhen(sleepTimer) .setUsesChronometer(true) .setChronometerCountDown(true) - .setWhen(System.currentTimeMillis() + (time - SystemClock.elapsedRealtime())) + .setWhen(sleepTimer ? System.currentTimeMillis() + (time - SystemClock.elapsedRealtime()) : 0) .addAction(previousAction) .addAction(playPauseAction) .addAction(nextAction) @@ -128,59 +103,19 @@ Notification getChronoNotification(Bitmap bitmap, int color) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).coloredNotification()) { builder.setColor(color); } - return builder.build(); - } - - Notification getLegacyNotification(Bitmap bitmap, int color) { - 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.Action sleepTimerAction = new NotificationCompat.Action(R.drawable.ic_timer_white_24dp, - service.getString(R.string.action_sleep_timer), - sleepTimerIntent); - NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) - .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) - .addAction(sleepTimerAction); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setStyle(new MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).coloredNotification()) - builder.setColor(color); - } - return builder.build(); + 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 boolean isSleepTimerActive() { - return PendingIntent.getService(service, 0, stopPlayingIntent(), PendingIntent.FLAG_NO_CREATE) != null; - } - - private Intent stopPlayingIntent() { - return new Intent(service, MusicService.class).setAction(MusicService.ACTION_QUIT); + @NonNull + private Notification.Builder builder() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return new Notification.Builder(service, NOTIFICATION_CHANNEL_ID); + } + return new Notification.Builder(service); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/SleepTimerUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/SleepTimerUtil.java new file mode 100644 index 000000000..458b6c7c0 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/util/SleepTimerUtil.java @@ -0,0 +1,46 @@ +package com.kabouzeid.gramophone.util; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; + +import com.kabouzeid.gramophone.service.MusicService; + + +public class SleepTimerUtil { + + public static PendingIntent createTimer(Context context) { + return makeTimerIntent(context, PendingIntent.FLAG_CANCEL_CURRENT); + } + + public static Intent getTimerAction(Context context) { + return new Intent(context, MusicService.class).setAction(MusicService.ACTION_QUIT); + } + + /** + * Returns the current sleep timer, if any. + * @param context the context. + * @return the current timer. Returns {@code null} if the sleep timer is not running. + */ + public static PendingIntent getCurrentTimer(Context context) { + return makeTimerIntent(context, PendingIntent.FLAG_NO_CREATE); + } + + public static boolean isTimerRunning(Context context) { + PendingIntent running = getCurrentTimer(context); + + if (running != null) { + // AlarmManager does not seem to cancel intents after the alarm went off. We must + // therefore check if it expired to determine whether the sleep timer is actually + // running + return PreferenceUtil.getInstance(context).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime() > 0; + } + + return false; + } + + private static PendingIntent makeTimerIntent(Context context, int flag) { + return PendingIntent.getService(context, 110110110, getTimerAction(context), flag); + } +} diff --git a/app/src/main/res/layout/notification_big.xml b/app/src/main/res/layout/notification_big.xml index 50428d2f3..5d8f8aebb 100644 --- a/app/src/main/res/layout/notification_big.xml +++ b/app/src/main/res/layout/notification_big.xml @@ -14,7 +14,7 @@ ~ limitations under the License --> - + - - @@ -66,17 +54,37 @@ android:singleLine="true" android:textAppearance="@style/Theme.Phonograph.Notification.Title" /> - + android:layout_gravity="fill" + android:columnCount="2" + android:rowCount="1"> + + + + +