17
17
18
18
import static android .app .Service .STOP_FOREGROUND_DETACH ;
19
19
import static android .app .Service .STOP_FOREGROUND_REMOVE ;
20
+ import static androidx .media3 .common .util .Assertions .checkNotNull ;
20
21
import static java .util .concurrent .TimeUnit .MILLISECONDS ;
21
22
22
23
import android .annotation .SuppressLint ;
65
66
private final Handler mainHandler ;
66
67
private final Executor mainExecutor ;
67
68
private final Intent startSelfIntent ;
68
- private final Map <MediaSession , ListenableFuture < MediaController > > controllerMap ;
69
+ private final Map <MediaSession , ControllerInfo > controllerMap ;
69
70
70
71
private int totalNotificationCount ;
71
72
@ Nullable private MediaNotification mediaNotification ;
@@ -104,7 +105,7 @@ public void addSession(MediaSession session) {
104
105
.setListener (listener )
105
106
.setApplicationLooper (Looper .getMainLooper ())
106
107
.buildAsync ();
107
- controllerMap .put (session , controllerFuture );
108
+ controllerMap .put (session , new ControllerInfo ( controllerFuture ) );
108
109
controllerFuture .addListener (
109
110
() -> {
110
111
try {
@@ -123,9 +124,9 @@ public void addSession(MediaSession session) {
123
124
}
124
125
125
126
public void removeSession (MediaSession session ) {
126
- @ Nullable ListenableFuture < MediaController > future = controllerMap .remove (session );
127
- if (future != null ) {
128
- MediaController .releaseFuture (future );
127
+ @ Nullable ControllerInfo controllerInfo = controllerMap .remove (session );
128
+ if (controllerInfo != null ) {
129
+ MediaController .releaseFuture (controllerInfo . controllerFuture );
129
130
}
130
131
}
131
132
@@ -158,19 +159,8 @@ public void updateNotification(MediaSession session, boolean startInForegroundRe
158
159
}
159
160
160
161
int notificationSequence = ++totalNotificationCount ;
161
- MediaController mediaNotificationController = null ;
162
- ListenableFuture <MediaController > controller = controllerMap .get (session );
163
- if (controller != null && controller .isDone ()) {
164
- try {
165
- mediaNotificationController = Futures .getDone (controller );
166
- } catch (ExecutionException e ) {
167
- // Ignore.
168
- }
169
- }
170
162
ImmutableList <CommandButton > mediaButtonPreferences =
171
- mediaNotificationController != null
172
- ? mediaNotificationController .getMediaButtonPreferences ()
173
- : ImmutableList .of ();
163
+ checkNotNull (getConnectedControllerForSession (session )).getMediaButtonPreferences ();
174
164
MediaNotification .Provider .Callback callback =
175
165
notification ->
176
166
mainExecutor .execute (
@@ -261,6 +251,13 @@ private void onNotificationUpdated(
261
251
}
262
252
}
263
253
254
+ private void onNotificationDismissed (MediaSession session ) {
255
+ @ Nullable ControllerInfo controllerInfo = controllerMap .get (session );
256
+ if (controllerInfo != null ) {
257
+ controllerInfo .wasNotificationDismissed = true ;
258
+ }
259
+ }
260
+
264
261
// POST_NOTIFICATIONS permission is not required for media session related notifications.
265
262
// https://developer.android.com/develop/ui/views/notifications/notification-permission#exemptions-media-sessions
266
263
@ SuppressLint ("MissingPermission" )
@@ -270,8 +267,7 @@ private void updateNotificationInternal(
270
267
boolean startInForegroundRequired ) {
271
268
// Call Notification.MediaStyle#setMediaSession() indirectly.
272
269
android .media .session .MediaSession .Token fwkToken =
273
- (android .media .session .MediaSession .Token )
274
- session .getSessionCompat ().getSessionToken ().getToken ();
270
+ session .getSessionCompat ().getSessionToken ().getToken ();
275
271
mediaNotification .notification .extras .putParcelable (Notification .EXTRA_MEDIA_SESSION , fwkToken );
276
272
this .mediaNotification = mediaNotification ;
277
273
if (startInForegroundRequired ) {
@@ -301,17 +297,25 @@ private void removeNotification() {
301
297
302
298
private boolean shouldShowNotification (MediaSession session ) {
303
299
MediaController controller = getConnectedControllerForSession (session );
304
- return controller != null && !controller .getCurrentTimeline ().isEmpty ();
300
+ if (controller == null || controller .getCurrentTimeline ().isEmpty ()) {
301
+ return false ;
302
+ }
303
+ ControllerInfo controllerInfo = checkNotNull (controllerMap .get (session ));
304
+ if (controller .getPlaybackState () != Player .STATE_IDLE ) {
305
+ // Playback restarted, reset previous notification dismissed flag.
306
+ controllerInfo .wasNotificationDismissed = false ;
307
+ }
308
+ return !controllerInfo .wasNotificationDismissed ;
305
309
}
306
310
307
311
@ Nullable
308
312
private MediaController getConnectedControllerForSession (MediaSession session ) {
309
- ListenableFuture < MediaController > controller = controllerMap .get (session );
310
- if (controller == null || !controller .isDone ()) {
313
+ @ Nullable ControllerInfo controllerInfo = controllerMap .get (session );
314
+ if (controllerInfo == null || !controllerInfo . controllerFuture .isDone ()) {
311
315
return null ;
312
316
}
313
317
try {
314
- return Futures .getDone (controller );
318
+ return Futures .getDone (controllerInfo . controllerFuture );
315
319
} catch (ExecutionException exception ) {
316
320
// We should never reach this.
317
321
throw new IllegalStateException (exception );
@@ -350,8 +354,7 @@ public void onFailure(Throwable t) {
350
354
}
351
355
}
352
356
353
- private static final class MediaControllerListener
354
- implements MediaController .Listener , Player .Listener {
357
+ private final class MediaControllerListener implements MediaController .Listener , Player .Listener {
355
358
private final MediaSessionService mediaSessionService ;
356
359
private final MediaSession session ;
357
360
@@ -381,6 +384,17 @@ public void onAvailableSessionCommandsChanged(
381
384
session , /* startInForegroundWhenPaused= */ false );
382
385
}
383
386
387
+ @ Override
388
+ public ListenableFuture <SessionResult > onCustomCommand (
389
+ MediaController controller , SessionCommand command , Bundle args ) {
390
+ @ SessionResult .Code int resultCode = SessionError .ERROR_NOT_SUPPORTED ;
391
+ if (command .customAction .equals (MediaNotification .NOTIFICATION_DISMISSED_EVENT_KEY )) {
392
+ onNotificationDismissed (session );
393
+ resultCode = SessionResult .RESULT_SUCCESS ;
394
+ }
395
+ return Futures .immediateFuture (new SessionResult (resultCode ));
396
+ }
397
+
384
398
@ Override
385
399
public void onDisconnected (MediaController controller ) {
386
400
if (mediaSessionService .isSessionAdded (session )) {
@@ -427,6 +441,18 @@ private void stopForeground(boolean removeNotifications) {
427
441
startedInForeground = false ;
428
442
}
429
443
444
+ private static final class ControllerInfo {
445
+
446
+ public final ListenableFuture <MediaController > controllerFuture ;
447
+
448
+ /** Indicates whether the user actively dismissed the notification. */
449
+ public boolean wasNotificationDismissed ;
450
+
451
+ public ControllerInfo (ListenableFuture <MediaController > controllerFuture ) {
452
+ this .controllerFuture = controllerFuture ;
453
+ }
454
+ }
455
+
430
456
@ RequiresApi (24 )
431
457
private static class Api24 {
432
458
0 commit comments