Merge pull request #3497 from ByteHamster/position-in-notification

Showing progress in notification
This commit is contained in:
H. Lehmann 2019-10-06 19:42:14 +02:00 committed by GitHub
commit 0e614f96e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 118 deletions

View File

@ -216,6 +216,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private PlaybackServiceFlavorHelper flavorHelper; private PlaybackServiceFlavorHelper flavorHelper;
private PlaybackServiceStateManager stateManager; private PlaybackServiceStateManager stateManager;
private Disposable positionEventTimer; private Disposable positionEventTimer;
private PlaybackServiceNotificationBuilder notificationBuilder;
/** /**
* Used for Lollipop notifications, Android Wear, and Android Auto. * Used for Lollipop notifications, Android Wear, and Android Auto.
@ -271,7 +272,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
isRunning = true; isRunning = true;
stateManager = new PlaybackServiceStateManager(this); stateManager = new PlaybackServiceStateManager(this);
PlaybackServiceNotificationBuilder notificationBuilder = new PlaybackServiceNotificationBuilder(this); notificationBuilder = new PlaybackServiceNotificationBuilder(this);
stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build()); stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build());
registerReceiver(autoStateUpdated, new IntentFilter("com.google.android.gms.car.media.STATUS")); registerReceiver(autoStateUpdated, new IntentFilter("com.google.android.gms.car.media.STATUS"));
@ -444,20 +445,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId); super.onStartCommand(intent, flags, startId);
Log.d(TAG, "OnStartCommand called"); Log.d(TAG, "OnStartCommand called");
if (!stateManager.isInForeground()) { stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build());
PlaybackServiceNotificationBuilder notificationBuilder = new PlaybackServiceNotificationBuilder(this);
if (mediaPlayer != null && getPlayable() != null) {
notificationBuilder.addMetadata(getPlayable(), mediaSession.getSessionToken(), getStatus(), isCasting);
if (notificationBuilder.isIconCached(getPlayable())) {
notificationBuilder.loadIcon(getPlayable());
}
}
stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.cancel(NOTIFICATION_ID_STREAMING); notificationManager.cancel(NOTIFICATION_ID_STREAMING);
@ -1195,59 +1185,51 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} }
private synchronized void setupNotification(final Playable playable) { private synchronized void setupNotification(final Playable playable) {
Log.d(TAG, "setupNotification");
if (notificationSetupThread != null) { if (notificationSetupThread != null) {
notificationSetupThread.interrupt(); notificationSetupThread.interrupt();
} }
if (playable == null) { if (playable == null || mediaPlayer == null) {
Log.d(TAG, "setupNotification: playable is null" + Log.getStackTraceString(new Exception())); Log.d(TAG, "setupNotification: playable=" + playable);
Log.d(TAG, "setupNotification: mediaPlayer=" + mediaPlayer);
if (!stateManager.hasReceivedValidStartCommand()) { if (!stateManager.hasReceivedValidStartCommand()) {
stateManager.stopService(); stateManager.stopService();
} }
return; return;
} }
Runnable notificationSetupTask = new Runnable() {
@Override
public void run() {
Log.d(TAG, "Starting background work");
if (mediaPlayer == null) { PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
Log.d(TAG, "notificationSetupTask: mediaPlayer is null"); notificationBuilder.setMetadata(playable, mediaSession.getSessionToken(), playerStatus, isCasting);
if (!stateManager.hasReceivedValidStartCommand()) { notificationBuilder.updatePosition(getCurrentPosition(), getCurrentPlaybackSpeed());
stateManager.stopService();
} Log.d(TAG, "setupNotification: startForeground" + playerStatus);
return; NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
startForegroundIfPlaying(playerStatus);
if (!notificationBuilder.isIconCached()) {
notificationSetupThread = new Thread(() -> {
Log.d(TAG, "Loading notification icon");
notificationBuilder.loadIcon();
if (!Thread.currentThread().isInterrupted()) {
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
} }
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus(); });
PlaybackServiceNotificationBuilder notificationBuilder = notificationSetupThread.start();
new PlaybackServiceNotificationBuilder(PlaybackService.this); }
notificationBuilder.addMetadata(playable, mediaSession.getSessionToken(), playerStatus, isCasting); }
if (!notificationBuilder.isIconCached(playable)) { private void startForegroundIfPlaying(@NonNull PlayerStatus status) {
// To make sure that the notification is shown instantly if (stateManager.hasReceivedValidStartCommand()) {
notificationBuilder.loadDefaultIcon(); if (isCasting || status == PlayerStatus.PLAYING || status == PlayerStatus.PREPARING
stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build()); || status == PlayerStatus.SEEKING) {
} stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build());
notificationBuilder.loadIcon(playable); } else {
stateManager.stopForeground(false);
if (!Thread.currentThread().isInterrupted() && stateManager.hasReceivedValidStartCommand()) { NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
Notification notification = notificationBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
if (playerStatus == PlayerStatus.PLAYING ||
playerStatus == PlayerStatus.PREPARING ||
playerStatus == PlayerStatus.SEEKING ||
isCasting) {
stateManager.startForeground(NOTIFICATION_ID, notification);
} else {
stateManager.stopForeground(false);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
Log.d(TAG, "Notification set up");
}
} }
}; }
notificationSetupThread = new Thread(notificationSetupTask);
notificationSetupThread.start();
} }
/** /**
@ -1593,8 +1575,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.d(TAG, "Setting up position observer"); Log.d(TAG, "Setting up position observer");
positionEventTimer = Observable.interval(1, TimeUnit.SECONDS) positionEventTimer = Observable.interval(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(aLong -> .subscribe(number -> {
EventBus.getDefault().post(new PlaybackPositionEvent(getCurrentPosition(), getDuration()))); EventBus.getDefault().post(new PlaybackPositionEvent(getCurrentPosition(), getDuration()));
if (Build.VERSION.SDK_INT < 29) {
notificationBuilder.updatePosition(getCurrentPosition(), getCurrentPlaybackSpeed());
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
});
} }
private void cancelPositionObserver() { private void cancelPositionObserver() {

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.service.playback; package de.danoeh.antennapod.core.service.playback;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -10,78 +11,67 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.IntList; import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
import de.danoeh.antennapod.core.util.gui.NotificationUtils; import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
public class PlaybackServiceNotificationBuilder extends NotificationCompat.Builder { public class PlaybackServiceNotificationBuilder {
private static final String TAG = "PlaybackSrvNotification"; private static final String TAG = "PlaybackSrvNotification";
private static Bitmap defaultIcon = null; private static Bitmap defaultIcon = null;
private Context context; private Context context;
private Playable playable;
private MediaSessionCompat.Token mediaSessionToken;
private PlayerStatus playerStatus;
private boolean isCasting;
private Bitmap icon;
private String position;
public PlaybackServiceNotificationBuilder(@NonNull Context context) { public PlaybackServiceNotificationBuilder(@NonNull Context context) {
super(context, NotificationUtils.CHANNEL_ID_PLAYING);
this.context = context; this.context = context;
final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(context);
final PendingIntent pIntent = PendingIntent.getActivity(context, 0,
PlaybackService.getPlayerActivityIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT);
setContentTitle(context.getString(R.string.app_name));
setContentText("Service is running"); // Just in case the notification is not updated (should not occur)
setOngoing(false);
setContentIntent(pIntent);
setWhen(0); // we don't need the time
setSmallIcon(smallIcon);
setPriority(NotificationCompat.PRIORITY_MIN);
} }
public void addMetadata(Playable playable, MediaSessionCompat.Token mediaSessionToken, PlayerStatus playerStatus, boolean isCasting) { public void setMetadata(Playable playable, MediaSessionCompat.Token mediaSessionToken,
Log.v(TAG, "notificationSetupTask: playerStatus=" + playerStatus); PlayerStatus playerStatus, boolean isCasting) {
setContentTitle(playable.getFeedTitle());
setContentText(playable.getEpisodeTitle());
setPriority(UserPreferences.getNotifyPriority());
addActions(mediaSessionToken, playerStatus, isCasting);
setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
setColor(NotificationCompat.COLOR_DEFAULT);
}
public boolean isIconCached(Playable playable) { if (playable != this.playable) {
int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width); clearCache();
try {
Bitmap icon = Glide.with(context)
.asBitmap()
.load(playable.getImageLocation())
.apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
.apply(new RequestOptions()
.centerCrop()
.onlyRetrieveFromCache(true))
.submit(iconSize, iconSize)
.get();
return icon != null;
} catch (Throwable tr) {
return false;
} }
this.playable = playable;
this.mediaSessionToken = mediaSessionToken;
this.playerStatus = playerStatus;
this.isCasting = isCasting;
} }
public void loadIcon(Playable playable) { private void clearCache() {
Bitmap icon = null; this.icon = null;
this.position = null;
}
public void updatePosition(int position,float speed) {
TimeSpeedConverter converter = new TimeSpeedConverter(speed);
this.position = Converter.getDurationStringLong(converter.convert(position));
}
public boolean isIconCached() {
return icon != null;
}
public void loadIcon() {
int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width); int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
try { try {
icon = Glide.with(context) icon = Glide.with(context)
@ -94,19 +84,13 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "Error loading the media icon for the notification", tr); Log.e(TAG, "Error loading the media icon for the notification", tr);
} }
if (icon == null) {
loadDefaultIcon();
} else {
setLargeIcon(icon);
}
} }
public void loadDefaultIcon() { private Bitmap getDefaultIcon() {
if (defaultIcon == null) { if (defaultIcon == null) {
defaultIcon = getBitmap(context, R.drawable.notification_default_large_icon); defaultIcon = getBitmap(context, R.drawable.notification_default_large_icon);
} }
setLargeIcon(defaultIcon); return defaultIcon;
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ -130,7 +114,47 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
} }
} }
private void addActions(MediaSessionCompat.Token mediaSessionToken, PlayerStatus playerStatus, boolean isCasting) { public Notification build() {
NotificationCompat.Builder notification = new NotificationCompat.Builder(context,
NotificationUtils.CHANNEL_ID_PLAYING);
if (playable != null) {
notification.setContentTitle(playable.getFeedTitle());
notification.setContentText(playable.getEpisodeTitle());
addActions(notification, mediaSessionToken, playerStatus, isCasting);
if (icon != null) {
notification.setLargeIcon(icon);
} else {
notification.setLargeIcon(getDefaultIcon());
}
if (Build.VERSION.SDK_INT < 29) {
notification.setSubText(position);
}
} else {
notification.setContentTitle(context.getString(R.string.app_name));
notification.setContentText("Service is running");
}
notification.setContentIntent(getPlayerActivityPendingIntent());
notification.setWhen(0);
notification.setSmallIcon(R.drawable.ic_antenna);
notification.setOngoing(false);
notification.setOnlyAlertOnce(true);
notification.setPriority(UserPreferences.getNotifyPriority());
notification.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
notification.setColor(NotificationCompat.COLOR_DEFAULT);
return notification.build();
}
private PendingIntent getPlayerActivityPendingIntent() {
return PendingIntent.getActivity(context, 0, PlaybackService.getPlayerActivityIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT);
}
private void addActions(NotificationCompat.Builder notification, MediaSessionCompat.Token mediaSessionToken,
PlayerStatus playerStatus, boolean isCasting) {
IntList compactActionList = new IntList(); IntList compactActionList = new IntList();
int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
@ -140,7 +164,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true); stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true);
PendingIntent stopCastingPendingIntent = PendingIntent.getService(context, PendingIntent stopCastingPendingIntent = PendingIntent.getService(context,
numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
addAction(R.drawable.ic_notification_cast_off, notification.addAction(R.drawable.ic_notification_cast_off,
context.getString(R.string.cast_disconnect_label), context.getString(R.string.cast_disconnect_label),
stopCastingPendingIntent); stopCastingPendingIntent);
numActions++; numActions++;
@ -149,7 +173,8 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
// always let them rewind // always let them rewind
PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_REWIND, numActions); KeyEvent.KEYCODE_MEDIA_REWIND, numActions);
addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label), rewindButtonPendingIntent); notification.addAction(R.drawable.ic_notification_fast_rewind, context.getString(R.string.rewind_label),
rewindButtonPendingIntent);
if (UserPreferences.showRewindOnCompactNotification()) { if (UserPreferences.showRewindOnCompactNotification()) {
compactActionList.add(numActions); compactActionList.add(numActions);
} }
@ -158,14 +183,14 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
if (playerStatus == PlayerStatus.PLAYING) { if (playerStatus == PlayerStatus.PLAYING) {
PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_PAUSE, numActions); KeyEvent.KEYCODE_MEDIA_PAUSE, numActions);
addAction(R.drawable.ic_notification_pause, //pause action notification.addAction(R.drawable.ic_notification_pause, //pause action
context.getString(R.string.pause_label), context.getString(R.string.pause_label),
pauseButtonPendingIntent); pauseButtonPendingIntent);
compactActionList.add(numActions++); compactActionList.add(numActions++);
} else { } else {
PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_PLAY, numActions); KeyEvent.KEYCODE_MEDIA_PLAY, numActions);
addAction(R.drawable.ic_notification_play, //play action notification.addAction(R.drawable.ic_notification_play, //play action
context.getString(R.string.play_label), context.getString(R.string.play_label),
playButtonPendingIntent); playButtonPendingIntent);
compactActionList.add(numActions++); compactActionList.add(numActions++);
@ -174,7 +199,8 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
// ff follows play, then we have skip (if it's present) // ff follows play, then we have skip (if it's present)
PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions); KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions);
addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label), ffButtonPendingIntent); notification.addAction(R.drawable.ic_notification_fast_forward, context.getString(R.string.fast_forward_label),
ffButtonPendingIntent);
if (UserPreferences.showFastForwardOnCompactNotification()) { if (UserPreferences.showFastForwardOnCompactNotification()) {
compactActionList.add(numActions); compactActionList.add(numActions);
} }
@ -183,7 +209,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
if (UserPreferences.isFollowQueue()) { if (UserPreferences.isFollowQueue()) {
PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_NEXT, numActions); KeyEvent.KEYCODE_MEDIA_NEXT, numActions);
addAction(R.drawable.ic_notification_skip, notification.addAction(R.drawable.ic_notification_skip,
context.getString(R.string.skip_episode_label), context.getString(R.string.skip_episode_label),
skipButtonPendingIntent); skipButtonPendingIntent);
if (UserPreferences.showSkipOnCompactNotification()) { if (UserPreferences.showSkipOnCompactNotification()) {
@ -194,7 +220,7 @@ public class PlaybackServiceNotificationBuilder extends NotificationCompat.Build
PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction( PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_STOP, numActions); KeyEvent.KEYCODE_MEDIA_STOP, numActions);
setStyle(new androidx.media.app.NotificationCompat.MediaStyle() notification.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSessionToken) .setMediaSession(mediaSessionToken)
.setShowActionsInCompactView(compactActionList.toArray()) .setShowActionsInCompactView(compactActionList.toArray())
.setShowCancelButton(true) .setShowCancelButton(true)