Add setRemotePlaybackInfo to MediaStyle

This method is needed for some system apps to override the
output switcher when MediaRouter2 can't be used.

PiperOrigin-RevId: 600807119
This commit is contained in:
tonihei 2024-01-23 09:03:29 -08:00 committed by Copybara-Service
parent 1f78aa5b2a
commit b64d754670

View File

@ -15,8 +15,12 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
import static androidx.core.app.NotificationCompat.COLOR_DEFAULT; import static androidx.core.app.NotificationCompat.COLOR_DEFAULT;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.os.Build; import android.os.Build;
@ -25,13 +29,16 @@ import android.support.v4.media.session.MediaSessionCompat;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import androidx.annotation.DoNotInline; import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.core.app.NotificationBuilderWithBuilderAccessor; import androidx.core.app.NotificationBuilderWithBuilderAccessor;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Class containing media specfic {@link androidx.core.app.NotificationCompat.Style styles} that you * Class containing media specfic {@link androidx.core.app.NotificationCompat.Style styles} that you
@ -103,10 +110,14 @@ public class MediaStyleNotificationHelper {
private static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; private static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
private static final int MAX_MEDIA_BUTTONS = 5; private static final int MAX_MEDIA_BUTTONS = 5;
/* package */ MediaSession session; /* package */ final MediaSession session;
/* package */ boolean showCancelButton;
private boolean showCancelButton;
/* package */ int @NullableType [] actionsToShowInCompact; /* package */ int @NullableType [] actionsToShowInCompact;
/* package */ @Nullable PendingIntent cancelButtonIntent; @Nullable /* package */ PendingIntent cancelButtonIntent;
/* package */ @MonotonicNonNull CharSequence remoteDeviceName;
/* package */ int remoteDeviceIconRes;
@Nullable /* package */ PendingIntent remoteDeviceIntent;
/** /**
* Creates a new instance with a {@link MediaSession} to this Notification to provide additional * Creates a new instance with a {@link MediaSession} to this Notification to provide additional
@ -172,9 +183,52 @@ public class MediaStyleNotificationHelper {
return this; return this;
} }
/**
* For media notifications associated with playback on a remote device, provide device
* information that will replace the default values for the output switcher chip on the media
* control, as well as an intent to use when the output switcher chip is tapped, on devices
* where this is supported.
*
* <p>Most apps should integrate with {@link android.media.MediaRouter2} instead. This method is
* only intended for system applications to provide information and/or functionality that would
* otherwise be unavailable to the default output switcher because the media originated on a
* remote device.
*
* <p>Also note that this method is a no-op when running on API 33 or lower.
*
* @param deviceName The name of the remote device to display.
* @param iconResource Icon resource, of size 12, representing the device.
* @param chipIntent PendingIntent to send when the output switcher is tapped. May be {@code
* null}, in which case the output switcher will be disabled. This intent should open an
* {@link android.app.Activity} or it will be ignored.
*/
@CanIgnoreReturnValue
@RequiresPermission(MEDIA_CONTENT_CONTROL)
public MediaStyle setRemotePlaybackInfo(
CharSequence deviceName,
@DrawableRes int iconResource,
@Nullable PendingIntent chipIntent) {
checkArgument(deviceName != null);
this.remoteDeviceName = deviceName;
this.remoteDeviceIconRes = iconResource;
this.remoteDeviceIntent = chipIntent;
return this;
}
@Override @Override
public void apply(NotificationBuilderWithBuilderAccessor builder) { public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 34 && remoteDeviceName != null) {
Api21Impl.setMediaStyle(
builder.getBuilder(),
Api21Impl.fillInMediaStyle(
Api34Impl.setRemotePlaybackInfo(
Api21Impl.createMediaStyle(),
remoteDeviceName,
remoteDeviceIconRes,
remoteDeviceIntent),
actionsToShowInCompact,
session));
} else if (Util.SDK_INT >= 21) {
Api21Impl.setMediaStyle( Api21Impl.setMediaStyle(
builder.getBuilder(), builder.getBuilder(),
Api21Impl.fillInMediaStyle( Api21Impl.fillInMediaStyle(
@ -191,7 +245,7 @@ public class MediaStyleNotificationHelper {
@Nullable @Nullable
@SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable @SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) { public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// No custom content view required // No custom content view required
return null; return null;
} }
@ -265,7 +319,7 @@ public class MediaStyleNotificationHelper {
@Nullable @Nullable
@SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable @SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) { public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// No custom content view required // No custom content view required
return null; return null;
} }
@ -352,8 +406,18 @@ public class MediaStyleNotificationHelper {
@Override @Override
public void apply(NotificationBuilderWithBuilderAccessor builder) { public void apply(NotificationBuilderWithBuilderAccessor builder) {
if (Util.SDK_INT >= 34 && remoteDeviceName != null) {
if (Build.VERSION.SDK_INT >= 24) { Api21Impl.setMediaStyle(
builder.getBuilder(),
Api21Impl.fillInMediaStyle(
Api34Impl.setRemotePlaybackInfo(
Api24Impl.createDecoratedMediaCustomViewStyle(),
remoteDeviceName,
remoteDeviceIconRes,
remoteDeviceIntent),
actionsToShowInCompact,
session));
} else if (Util.SDK_INT >= 24) {
Api21Impl.setMediaStyle( Api21Impl.setMediaStyle(
builder.getBuilder(), builder.getBuilder(),
Api21Impl.fillInMediaStyle( Api21Impl.fillInMediaStyle(
@ -370,12 +434,12 @@ public class MediaStyleNotificationHelper {
@Nullable @Nullable
@SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable @SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable
public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) { public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) { if (Util.SDK_INT >= 24) {
// No custom content view required // No custom content view required
return null; return null;
} }
boolean hasContentView = mBuilder.getContentView() != null; boolean hasContentView = mBuilder.getContentView() != null;
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// If we are on L/M the media notification will only be colored if the expanded // If we are on L/M the media notification will only be colored if the expanded
// version is of media style, so we have to create a custom view for the collapsed // version is of media style, so we have to create a custom view for the collapsed
// version as well in that case. // version as well in that case.
@ -409,7 +473,7 @@ public class MediaStyleNotificationHelper {
@Nullable @Nullable
@SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable @SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable
public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) { public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) { if (Util.SDK_INT >= 24) {
// No custom big content view required // No custom big content view required
return null; return null;
} }
@ -423,7 +487,7 @@ public class MediaStyleNotificationHelper {
} }
RemoteViews bigContentView = generateBigContentView(); RemoteViews bigContentView = generateBigContentView();
buildIntoRemoteViews(bigContentView, innerView); buildIntoRemoteViews(bigContentView, innerView);
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
setBackgroundColor(bigContentView); setBackgroundColor(bigContentView);
} }
return bigContentView; return bigContentView;
@ -440,7 +504,7 @@ public class MediaStyleNotificationHelper {
@Nullable @Nullable
@SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable @SuppressWarnings("nullness:override.return") // NotificationCompat doesn't annotate @Nullable
public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) { public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
if (Build.VERSION.SDK_INT >= 24) { if (Util.SDK_INT >= 24) {
// No custom heads up content view required // No custom heads up content view required
return null; return null;
} }
@ -454,7 +518,7 @@ public class MediaStyleNotificationHelper {
} }
RemoteViews headsUpContentView = generateBigContentView(); RemoteViews headsUpContentView = generateBigContentView();
buildIntoRemoteViews(headsUpContentView, innerView); buildIntoRemoteViews(headsUpContentView, innerView);
if (Build.VERSION.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
setBackgroundColor(headsUpContentView); setBackgroundColor(headsUpContentView);
} }
return headsUpContentView; return headsUpContentView;
@ -493,8 +557,8 @@ public class MediaStyleNotificationHelper {
Notification.MediaStyle style, Notification.MediaStyle style,
@Nullable int[] actionsToShowInCompact, @Nullable int[] actionsToShowInCompact,
MediaSession session) { MediaSession session) {
Assertions.checkNotNull(style); checkNotNull(style);
Assertions.checkNotNull(session); checkNotNull(session);
if (actionsToShowInCompact != null) { if (actionsToShowInCompact != null) {
setShowActionsInCompactView(style, actionsToShowInCompact); setShowActionsInCompactView(style, actionsToShowInCompact);
} }
@ -518,4 +582,23 @@ public class MediaStyleNotificationHelper {
return new Notification.DecoratedMediaCustomViewStyle(); return new Notification.DecoratedMediaCustomViewStyle();
} }
} }
@RequiresApi(34)
private static class Api34Impl {
private Api34Impl() {}
// MEDIA_CONTENT_CONTROL permission is required by setRemotePlaybackInfo
@CanIgnoreReturnValue
@SuppressLint({"MissingPermission"})
@DoNotInline
public static Notification.MediaStyle setRemotePlaybackInfo(
Notification.MediaStyle style,
CharSequence remoteDeviceName,
@DrawableRes int remoteDeviceIconRes,
@Nullable PendingIntent remoteDeviceIntent) {
style.setRemotePlaybackInfo(remoteDeviceName, remoteDeviceIconRes, remoteDeviceIntent);
return style;
}
}
} }