Avoid usage of MediaController in MediaNotification.Provider
PiperOrigin-RevId: 451155897
This commit is contained in:
parent
3d2b335825
commit
821615cea0
@ -51,7 +51,6 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -126,16 +125,17 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final MediaNotification createNotification(
|
public final MediaNotification createNotification(
|
||||||
MediaController mediaController,
|
MediaSession mediaSession,
|
||||||
ImmutableList<CommandButton> customLayout,
|
ImmutableList<CommandButton> customLayout,
|
||||||
MediaNotification.ActionFactory actionFactory,
|
MediaNotification.ActionFactory actionFactory,
|
||||||
Callback onNotificationChangedCallback) {
|
Callback onNotificationChangedCallback) {
|
||||||
ensureNotificationChannel();
|
ensureNotificationChannel();
|
||||||
|
|
||||||
|
Player player = mediaSession.getPlayer();
|
||||||
NotificationCompat.Builder builder =
|
NotificationCompat.Builder builder =
|
||||||
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
// Set metadata info in the notification.
|
// Set metadata info in the notification.
|
||||||
MediaMetadata metadata = mediaController.getMediaMetadata();
|
MediaMetadata metadata = player.getMediaMetadata();
|
||||||
builder.setContentTitle(metadata.title).setContentText(metadata.artist);
|
builder.setContentTitle(metadata.title).setContentText(metadata.artist);
|
||||||
|
|
||||||
@Nullable ListenableFuture<Bitmap> bitmapFuture = loadArtworkBitmap(metadata);
|
@Nullable ListenableFuture<Bitmap> bitmapFuture = loadArtworkBitmap(metadata);
|
||||||
@ -164,19 +164,16 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
|||||||
MediaStyle mediaStyle = new MediaStyle();
|
MediaStyle mediaStyle = new MediaStyle();
|
||||||
int[] compactViewIndices =
|
int[] compactViewIndices =
|
||||||
addNotificationActions(
|
addNotificationActions(
|
||||||
getMediaButtons(
|
getMediaButtons(player.getAvailableCommands(), customLayout, player.getPlayWhenReady()),
|
||||||
mediaController.getAvailableCommands(),
|
|
||||||
customLayout,
|
|
||||||
mediaController.getPlayWhenReady()),
|
|
||||||
builder,
|
builder,
|
||||||
actionFactory);
|
actionFactory);
|
||||||
mediaStyle.setShowActionsInCompactView(compactViewIndices);
|
mediaStyle.setShowActionsInCompactView(compactViewIndices);
|
||||||
if (mediaController.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) {
|
if (player.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) {
|
||||||
// We must include a cancel intent for pre-L devices.
|
// We must include a cancel intent for pre-L devices.
|
||||||
mediaStyle.setCancelButtonIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP));
|
mediaStyle.setCancelButtonIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP));
|
||||||
}
|
}
|
||||||
|
|
||||||
long playbackStartTimeMs = getPlaybackStartTimeEpochMs(mediaController);
|
long playbackStartTimeMs = getPlaybackStartTimeEpochMs(player);
|
||||||
boolean displayElapsedTimeWithChronometer = playbackStartTimeMs != C.TIME_UNSET;
|
boolean displayElapsedTimeWithChronometer = playbackStartTimeMs != C.TIME_UNSET;
|
||||||
builder
|
builder
|
||||||
.setWhen(playbackStartTimeMs)
|
.setWhen(playbackStartTimeMs)
|
||||||
@ -185,7 +182,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
|||||||
|
|
||||||
Notification notification =
|
Notification notification =
|
||||||
builder
|
builder
|
||||||
.setContentIntent(mediaController.getSessionActivity())
|
.setContentIntent(mediaSession.getSessionActivity())
|
||||||
.setDeleteIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP))
|
.setDeleteIntent(actionFactory.createMediaActionPendingIntent(COMMAND_STOP))
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setSmallIcon(getSmallIconResId(context))
|
.setSmallIcon(getSmallIconResId(context))
|
||||||
@ -197,34 +194,9 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleCustomCommand(
|
public final boolean handleCustomCommand(MediaSession session, String action, Bundle extras) {
|
||||||
MediaController mediaController, String action, Bundle extras) {
|
// Make the custom action being delegated to the session as a custom session command.
|
||||||
@Nullable SessionCommand customCommand = null;
|
return false;
|
||||||
for (SessionCommand command : mediaController.getAvailableSessionCommands().commands) {
|
|
||||||
if (command.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
|
|
||||||
&& command.customAction.equals(action)) {
|
|
||||||
customCommand = command;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (customCommand != null) {
|
|
||||||
ListenableFuture<SessionResult> future =
|
|
||||||
mediaController.sendCustomCommand(customCommand, extras);
|
|
||||||
Futures.addCallback(
|
|
||||||
future,
|
|
||||||
new FutureCallback<SessionResult>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(SessionResult result) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
Log.w(TAG, "custom command " + action + " produced an error: " + t.getMessage(), t);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MoreExecutors.directExecutor());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -422,14 +394,14 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long getPlaybackStartTimeEpochMs(MediaController controller) {
|
private static long getPlaybackStartTimeEpochMs(Player player) {
|
||||||
// Changing "showWhen" causes notification flicker if SDK_INT < 21.
|
// Changing "showWhen" causes notification flicker if SDK_INT < 21.
|
||||||
if (Util.SDK_INT >= 21
|
if (Util.SDK_INT >= 21
|
||||||
&& controller.isPlaying()
|
&& player.isPlaying()
|
||||||
&& !controller.isPlayingAd()
|
&& !player.isPlayingAd()
|
||||||
&& !controller.isCurrentMediaItemDynamic()
|
&& !player.isCurrentMediaItemDynamic()
|
||||||
&& controller.getPlaybackParameters().speed == 1f) {
|
&& player.getPlaybackParameters().speed == 1f) {
|
||||||
return System.currentTimeMillis() - controller.getContentPosition();
|
return System.currentTimeMillis() - player.getContentPosition();
|
||||||
} else {
|
} else {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ public final class MediaNotification {
|
|||||||
* <p>The returned {@link NotificationCompat.Action} will have a {@link PendingIntent} with the
|
* <p>The returned {@link NotificationCompat.Action} will have a {@link PendingIntent} with the
|
||||||
* extras from {@link SessionCommand#customExtras}. Accordingly the {@linkplain
|
* extras from {@link SessionCommand#customExtras}. Accordingly the {@linkplain
|
||||||
* SessionCommand#customExtras command's extras} will be passed to {@link
|
* SessionCommand#customExtras command's extras} will be passed to {@link
|
||||||
* Provider#handleCustomCommand(MediaController, String, Bundle)} when the action is executed.
|
* Provider#handleCustomCommand(MediaSession, String, Bundle)} when the action is executed.
|
||||||
*
|
*
|
||||||
* @param customCommandButton A {@linkplain CommandButton custom command button}.
|
* @param customCommandButton A {@linkplain CommandButton custom command button}.
|
||||||
* @see MediaNotification.Provider#handleCustomCommand
|
* @see MediaNotification.Provider#handleCustomCommand
|
||||||
@ -116,8 +116,8 @@ public final class MediaNotification {
|
|||||||
/**
|
/**
|
||||||
* Creates a new {@link MediaNotification}.
|
* Creates a new {@link MediaNotification}.
|
||||||
*
|
*
|
||||||
* @param mediaController The controller of the session.
|
* @param session The media session.
|
||||||
* @param actionFactory The {@link ActionFactory} for creating notification {@linkplain
|
* @param actionFactory The {@link ActionFactory} for creating notification {@link
|
||||||
* NotificationCompat.Action actions}.
|
* NotificationCompat.Action actions}.
|
||||||
* @param customLayout The custom layout {@linkplain MediaSession#setCustomLayout(List) set by
|
* @param customLayout The custom layout {@linkplain MediaSession#setCustomLayout(List) set by
|
||||||
* the session}.
|
* the session}.
|
||||||
@ -126,7 +126,7 @@ public final class MediaNotification {
|
|||||||
* been loaded asynchronously.
|
* been loaded asynchronously.
|
||||||
*/
|
*/
|
||||||
MediaNotification createNotification(
|
MediaNotification createNotification(
|
||||||
MediaController mediaController,
|
MediaSession session,
|
||||||
ImmutableList<CommandButton> customLayout,
|
ImmutableList<CommandButton> customLayout,
|
||||||
ActionFactory actionFactory,
|
ActionFactory actionFactory,
|
||||||
Callback onNotificationChangedCallback);
|
Callback onNotificationChangedCallback);
|
||||||
@ -134,13 +134,15 @@ public final class MediaNotification {
|
|||||||
/**
|
/**
|
||||||
* Handles a notification's custom command.
|
* Handles a notification's custom command.
|
||||||
*
|
*
|
||||||
* @param mediaController The controller of the session.
|
* @param session The media session.
|
||||||
* @param action The custom command action.
|
* @param action The custom command action.
|
||||||
* @param extras A bundle {@linkplain SessionCommand#customExtras set in the custom command},
|
* @param extras A bundle {@linkplain SessionCommand#customExtras set in the custom command},
|
||||||
* otherwise {@link Bundle#EMPTY}.
|
* otherwise {@link Bundle#EMPTY}.
|
||||||
|
* @return {@code false} if the action should be delivered to the session as a custom command or
|
||||||
|
* {@code true} if the action has been handled completely by the provider.
|
||||||
* @see ActionFactory#createCustomAction
|
* @see ActionFactory#createCustomAction
|
||||||
*/
|
*/
|
||||||
void handleCustomCommand(MediaController mediaController, String action, Bundle extras);
|
boolean handleCustomCommand(MediaSession session, String action, Bundle extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The notification id. */
|
/** The notification id. */
|
||||||
|
@ -32,8 +32,10 @@ import androidx.media3.common.Player;
|
|||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -122,28 +124,44 @@ import java.util.concurrent.TimeoutException;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
MediaController mediaController = controllerFuture.get(0, TimeUnit.MILLISECONDS);
|
MediaController mediaController = checkStateNotNull(Futures.getDone(controllerFuture));
|
||||||
mediaNotificationProvider.handleCustomCommand(mediaController, action, extras);
|
if (!mediaNotificationProvider.handleCustomCommand(session, action, extras)) {
|
||||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
@Nullable SessionCommand customCommand = null;
|
||||||
|
for (SessionCommand command : mediaController.getAvailableSessionCommands().commands) {
|
||||||
|
if (command.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
|
||||||
|
&& command.customAction.equals(action)) {
|
||||||
|
customCommand = command;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (customCommand != null
|
||||||
|
&& mediaController.getAvailableSessionCommands().contains(customCommand)) {
|
||||||
|
ListenableFuture<SessionResult> future =
|
||||||
|
mediaController.sendCustomCommand(customCommand, Bundle.EMPTY);
|
||||||
|
Futures.addCallback(
|
||||||
|
future,
|
||||||
|
new FutureCallback<SessionResult>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SessionResult result) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
Log.w(
|
||||||
|
TAG, "custom command " + action + " produced an error: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ExecutionException e) {
|
||||||
// We should never reach this.
|
// We should never reach this.
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNotification(MediaSession session) {
|
public void updateNotification(MediaSession session) {
|
||||||
@Nullable ListenableFuture<MediaController> controllerFuture = controllerMap.get(session);
|
|
||||||
if (controllerFuture == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaController mediaController;
|
|
||||||
try {
|
|
||||||
mediaController = checkStateNotNull(Futures.getDone(controllerFuture));
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
// We should never reach this point.
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mediaSessionService.isSessionAdded(session) || !canStartPlayback(session.getPlayer())) {
|
if (!mediaSessionService.isSessionAdded(session) || !canStartPlayback(session.getPlayer())) {
|
||||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||||
return;
|
return;
|
||||||
@ -157,10 +175,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
MediaNotification mediaNotification =
|
MediaNotification mediaNotification =
|
||||||
this.mediaNotificationProvider.createNotification(
|
this.mediaNotificationProvider.createNotification(
|
||||||
mediaController,
|
session, checkStateNotNull(customLayoutMap.get(session)), actionFactory, callback);
|
||||||
checkStateNotNull(customLayoutMap.get(session)),
|
|
||||||
actionFactory,
|
|
||||||
callback);
|
|
||||||
updateNotificationInternal(session, mediaNotification);
|
updateNotificationInternal(session, mediaNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import android.view.KeyEvent;
|
|||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.DeviceInfo;
|
import androidx.media3.common.DeviceInfo;
|
||||||
@ -247,6 +248,14 @@ public class MediaSession {
|
|||||||
* Sets a {@link PendingIntent} to launch an {@link android.app.Activity} for the {@link
|
* Sets a {@link PendingIntent} to launch an {@link android.app.Activity} for the {@link
|
||||||
* MediaSession}. This can be used as a quick link to an ongoing media screen.
|
* MediaSession}. This can be used as a quick link to an ongoing media screen.
|
||||||
*
|
*
|
||||||
|
* <p>A client can use this pending intent to start an activity belonging to this session. When
|
||||||
|
* this pending intent is for instance included in the notification {@linkplain
|
||||||
|
* NotificationCompat.Builder#setContentIntent(PendingIntent) as the content intent}, tapping
|
||||||
|
* the notification will open this activity.
|
||||||
|
*
|
||||||
|
* <p>See <a href="https://developer.android.com/training/notify-user/navigation">'Start an
|
||||||
|
* Activity from a Notification'</a> also.
|
||||||
|
*
|
||||||
* @param pendingIntent The pending intent.
|
* @param pendingIntent The pending intent.
|
||||||
* @return The builder to allow chaining.
|
* @return The builder to allow chaining.
|
||||||
*/
|
*/
|
||||||
@ -514,6 +523,17 @@ public class MediaSession {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link PendingIntent} to launch {@linkplain
|
||||||
|
* Builder#setSessionActivity(PendingIntent) the session activity} or null if not set.
|
||||||
|
*
|
||||||
|
* @return The {@link PendingIntent} to launch an activity belonging to the session.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public PendingIntent getSessionActivity() {
|
||||||
|
return impl.getSessionActivity();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the underlying {@link Player} for this session to dispatch incoming events to.
|
* Sets the underlying {@link Player} for this session to dispatch incoming events to.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user