Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Android Service lifecycle/notification management. #849

Open
wants to merge 4 commits into
base: major
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ private static int calculateInSampleSize(BitmapFactory.Options options, int reqW
private boolean notificationCreated;
private final Handler handler = new Handler(Looper.getMainLooper());
private VolumeProviderCompat volumeProvider;
private boolean isInForeground;

public AudioProcessingState getProcessingState() {
return processingState;
Expand Down Expand Up @@ -404,9 +405,9 @@ else if (errorMessage != null)
mediaSession.setShuffleMode(shuffleMode);
mediaSession.setCaptioningEnabled(captioningEnabled);

if (!wasPlaying && playing) {
if (!isInForeground && isActuallyPlaying()) {
enterPlayingState();
} else if (wasPlaying && !playing) {
} else if (isInForeground && !playing) {
exitPlayingState();
}

Expand Down Expand Up @@ -489,11 +490,12 @@ private Notification buildNotification() {
final MediaStyle style = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(compactActionIndices);
if (config.androidNotificationOngoing) {
style.setShowCancelButton(true);
style.setCancelButtonIntent(buildMediaButtonPendingIntent(PlaybackStateCompat.ACTION_STOP));
builder.setOngoing(true);
}
boolean ongoing = (config.androidNotificationOngoing != null)
? config.androidNotificationOngoing
: isActuallyPlaying();
style.setShowCancelButton(!ongoing);
style.setCancelButtonIntent(buildMediaButtonPendingIntent(PlaybackStateCompat.ACTION_STOP));
builder.setOngoing(ongoing);
builder.setStyle(style);
return builder.build();
}
Expand Down Expand Up @@ -548,14 +550,22 @@ private void updateNotification() {
}
}

private boolean isActuallyPlaying() {
return playing && (processingState == AudioProcessingState.loading
|| processingState == AudioProcessingState.buffering
|| processingState == AudioProcessingState.ready);
}
Comment on lines +553 to +557
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes playing=true and idle thing. It's also looks ok to exit foreground state when it's completed or error.

Copy link
Contributor

@nt4f04uNd nt4f04uNd Oct 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But other problem is if you try to stop the idle service - nothing happens, I'll try to think about this...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a comment in #847

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above is only used when entering foreground. When exiting foreground, it uses a broader condition (simply !playing).


private void enterPlayingState() {
ContextCompat.startForegroundService(this, new Intent(AudioService.this, AudioService.class));
if (!mediaSession.isActive())
mediaSession.setActive(true);

acquireWakeLock();
mediaSession.setSessionActivity(contentIntent);
internalStartForeground();
startForeground(NOTIFICATION_ID, buildNotification());
notificationCreated = true;
isInForeground = true;
}

private void exitPlayingState() {
Expand All @@ -567,11 +577,7 @@ private void exitPlayingState() {
private void exitForegroundState() {
stopForeground(false);
releaseWakeLock();
}

private void internalStartForeground() {
startForeground(NOTIFICATION_ID, buildNotification());
notificationCreated = true;
isInForeground = false;
}

private void acquireWakeLock() {
Expand All @@ -595,6 +601,7 @@ private void deactivateMediaSession() {
}
// Force cancellation of the notification
getNotificationManager().cancel(NOTIFICATION_ID);
notificationCreated = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of this change notification won't update when go ready -> idle -> ready, not sure why.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes I was wondering whether this would break anything, although the logic seemed right. That said, when I planned out this change, I originally wanted to get rid of notificationCreated completely so that it would be possible to display the notification even before entering the foreground state. Maybe with !idle being used to determine whether updateNotification should show the notification rather than notificationCreated.

}

private void releaseMediaSession() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class AudioServiceConfig {
public String androidNotificationIcon;
public boolean androidShowNotificationBadge;
public boolean androidNotificationClickStartsActivity;
public boolean androidNotificationOngoing;
public Boolean androidNotificationOngoing;
public boolean androidStopForegroundOnPause;
public int artDownscaleWidth;
public int artDownscaleHeight;
Expand All @@ -50,7 +50,7 @@ public AudioServiceConfig(Context context) {
androidNotificationIcon = preferences.getString(KEY_ANDROID_NOTIFICATION_ICON, "mipmap/ic_launcher");
androidShowNotificationBadge = preferences.getBoolean(KEY_ANDROID_SHOW_NOTIFICATION_BADGE, false);
androidNotificationClickStartsActivity = preferences.getBoolean(KEY_ANDROID_NOTIFICATION_CLICK_STARTS_ACTIVITY, true);
androidNotificationOngoing = preferences.getBoolean(KEY_ANDROID_NOTIFICATION_ONGOING, false);
androidNotificationOngoing = !preferences.contains(KEY_ANDROID_NOTIFICATION_ONGOING) ? null : new Boolean(preferences.getBoolean(KEY_ANDROID_NOTIFICATION_ONGOING, false));
androidStopForegroundOnPause = preferences.getBoolean(KEY_ANDROID_STOP_FOREGROUND_ON_PAUSE, true);
artDownscaleWidth = preferences.getInt(KEY_ART_DOWNSCALE_WIDTH, -1);
artDownscaleHeight = preferences.getInt(KEY_ART_DOWNSCALE_HEIGHT, -1);
Expand Down Expand Up @@ -100,7 +100,7 @@ public Bundle getBrowsableRootExtras() {
}

public void save() {
preferences.edit()
final SharedPreferences.Editor editor = preferences.edit()
.putBoolean(KEY_ANDROID_RESUME_ON_CLICK, androidResumeOnClick)
.putString(KEY_ANDROID_NOTIFICATION_CHANNEL_ID, androidNotificationChannelId)
.putString(KEY_ANDROID_NOTIFICATION_CHANNEL_NAME, androidNotificationChannelName)
Expand All @@ -114,7 +114,11 @@ public void save() {
.putInt(KEY_ART_DOWNSCALE_WIDTH, artDownscaleWidth)
.putInt(KEY_ART_DOWNSCALE_HEIGHT, artDownscaleHeight)
.putString(KEY_ACTIVITY_CLASS_NAME, activityClassName)
.putString(KEY_BROWSABLE_ROOT_EXTRAS, browsableRootExtras)
.apply();
.putString(KEY_BROWSABLE_ROOT_EXTRAS, browsableRootExtras);
if (androidNotificationOngoing != null)
editor.putBoolean(KEY_ANDROID_NOTIFICATION_ONGOING, androidNotificationOngoing);
else
editor.remove(KEY_ANDROID_NOTIFICATION_ONGOING);
editor.apply();
}
}
1 change: 0 additions & 1 deletion audio_service/example/lib/example_multiple_handlers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Future<void> main() async {
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(MyApp());
Expand Down
1 change: 0 additions & 1 deletion audio_service/example/lib/example_playlist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Future<void> main() async {
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(MyApp());
Expand Down
1 change: 0 additions & 1 deletion audio_service/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ Future<void> main() async {
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(MyApp());
Expand Down
16 changes: 12 additions & 4 deletions audio_service/lib/audio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3305,7 +3305,11 @@ class AudioServiceConfig {
/// If you set this to true, [androidStopForegroundOnPause] must be true as well,
/// otherwise this will not do anything, because when foreground service is active,
/// it forces notification to be ongoing.
final bool androidNotificationOngoing;
///
/// Leave this unset if you would like the plugin to automatically set the
/// ongoing status whenever your handler is playing audio (i.e.
/// [PlaybackState.playing] and [AudioProcessingState.ready] are true.)
final bool? androidNotificationOngoing;

/// Whether the Android service should switch to a lower priority state when
/// playback is paused allowing the user to swipe away the notification. Note
Expand Down Expand Up @@ -3354,7 +3358,7 @@ class AudioServiceConfig {
this.androidNotificationIcon = 'mipmap/ic_launcher',
this.androidShowNotificationBadge = false,
this.androidNotificationClickStartsActivity = true,
this.androidNotificationOngoing = false,
this.androidNotificationOngoing,
this.androidStopForegroundOnPause = true,
this.artDownscaleWidth,
this.artDownscaleHeight,
Expand All @@ -3364,7 +3368,9 @@ class AudioServiceConfig {
this.androidBrowsableRootExtras,
}) : assert((artDownscaleWidth != null) == (artDownscaleHeight != null)),
assert(
!androidNotificationOngoing || androidStopForegroundOnPause,
androidNotificationOngoing == null ||
!androidNotificationOngoing ||
androidStopForegroundOnPause,
'The androidNotificationOngoing will make no effect with androidStopForegroundOnPause set to false',
);

Expand All @@ -3379,7 +3385,9 @@ class AudioServiceConfig {
androidShowNotificationBadge: androidShowNotificationBadge,
androidNotificationClickStartsActivity:
androidNotificationClickStartsActivity,
androidNotificationOngoing: androidNotificationOngoing,
// TODO: update AudioServiceConfigMessage.androidNotificationOngoing
// to be nullable.
androidNotificationOngoing: androidNotificationOngoing ?? false,
androidStopForegroundOnPause: androidStopForegroundOnPause,
artDownscaleWidth: artDownscaleWidth,
artDownscaleHeight: artDownscaleHeight,
Expand Down