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">
+
+
+
+
+