Cleanup some dead code and lint warnings in session compat code

PiperOrigin-RevId: 736861977
This commit is contained in:
tonihei 2025-03-14 07:52:59 -07:00 committed by Copybara-Service
parent df489e2f94
commit 6b1a2aff98
20 changed files with 416 additions and 5600 deletions

View File

@ -104,10 +104,6 @@ import java.util.concurrent.TimeoutException;
private static final String TAG = "LegacyConversions"; private static final String TAG = "LegacyConversions";
// Stub BrowserRoot for accepting any connection here.
public static final BrowserRoot defaultBrowserRoot =
new BrowserRoot(MediaLibraryService.SERVICE_INTERFACE, null);
public static final ImmutableSet<String> KNOWN_METADATA_COMPAT_KEYS = public static final ImmutableSet<String> KNOWN_METADATA_COMPAT_KEYS =
ImmutableSet.of( ImmutableSet.of(
MediaMetadataCompat.METADATA_KEY_TITLE, MediaMetadataCompat.METADATA_KEY_TITLE,
@ -1476,7 +1472,6 @@ import java.util.concurrent.TimeoutException;
if (state != null) { if (state != null) {
List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions(); List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions();
if (customActions != null) {
for (CustomAction customAction : customActions) { for (CustomAction customAction : customActions) {
String action = customAction.getAction(); String action = customAction.getAction();
@Nullable Bundle extras = customAction.getExtras(); @Nullable Bundle extras = customAction.getExtras();
@ -1484,7 +1479,6 @@ import java.util.concurrent.TimeoutException;
new SessionCommand(action, extras == null ? Bundle.EMPTY : extras)); new SessionCommand(action, extras == null ? Bundle.EMPTY : extras));
} }
} }
}
return sessionCommandsBuilder.build(); return sessionCommandsBuilder.build();
} }
@ -1504,9 +1498,6 @@ import java.util.concurrent.TimeoutException;
return ImmutableList.of(); return ImmutableList.of();
} }
List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions(); List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions();
if (customActions == null) {
return ImmutableList.of();
}
ImmutableList.Builder<CommandButton> customLayout = new ImmutableList.Builder<>(); ImmutableList.Builder<CommandButton> customLayout = new ImmutableList.Builder<>();
for (CustomAction customAction : customActions) { for (CustomAction customAction : customActions) {
String action = customAction.getAction(); String action = customAction.getAction();

View File

@ -422,6 +422,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
return options; return options;
} }
@Nullable
private static Bundle getExtras(@Nullable LibraryParams params) { private static Bundle getExtras(@Nullable LibraryParams params) {
return params != null ? params.extras : null; return params != null ? params.extras : null;
} }

View File

@ -450,7 +450,7 @@ public final class SessionToken {
private static MediaSessionCompat.Token createCompatToken( private static MediaSessionCompat.Token createCompatToken(
Parcelable platformOrLegacyCompatToken) { Parcelable platformOrLegacyCompatToken) {
if (platformOrLegacyCompatToken instanceof Token) { if (platformOrLegacyCompatToken instanceof Token) {
return MediaSessionCompat.Token.fromToken(platformOrLegacyCompatToken); return MediaSessionCompat.Token.fromToken((Token) platformOrLegacyCompatToken);
} }
// Assume this is an android.support.v4.media.session.MediaSessionCompat.Token. // Assume this is an android.support.v4.media.session.MediaSessionCompat.Token.
return LegacyParcelableUtil.convert( return LegacyParcelableUtil.convert(

View File

@ -27,12 +27,9 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
/** /**
@ -68,7 +65,6 @@ import java.util.Objects;
@UnstableApi @UnstableApi
@RestrictTo(LIBRARY) @RestrictTo(LIBRARY)
public class AudioAttributesCompat { public class AudioAttributesCompat {
static final String TAG = "AudioAttributesCompat";
/** Content type value to use when the content type is unknown, or other than the ones defined. */ /** Content type value to use when the content type is unknown, or other than the ones defined. */
public static final int CONTENT_TYPE_UNKNOWN = AudioAttributes.CONTENT_TYPE_UNKNOWN; public static final int CONTENT_TYPE_UNKNOWN = AudioAttributes.CONTENT_TYPE_UNKNOWN;
@ -183,10 +179,6 @@ public class AudioAttributesCompat {
private static final int SUPPRESSIBLE_CALL = 2; private static final int SUPPRESSIBLE_CALL = 2;
private static final SparseIntArray SUPPRESSIBLE_USAGES; private static final SparseIntArray SUPPRESSIBLE_USAGES;
// used by tests
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean sForceLegacyBehavior;
static { static {
SUPPRESSIBLE_USAGES = new SparseIntArray(); SUPPRESSIBLE_USAGES = new SparseIntArray();
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION, SUPPRESSIBLE_NOTIFICATION); SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION, SUPPRESSIBLE_NOTIFICATION);
@ -220,58 +212,15 @@ public class AudioAttributesCompat {
/** Flag defining a behavior where the audibility of the sound will be ensured by the system. */ /** Flag defining a behavior where the audibility of the sound will be ensured by the system. */
public static final int FLAG_AUDIBILITY_ENFORCED = 0x1 << 0; public static final int FLAG_AUDIBILITY_ENFORCED = 0x1 << 0;
static final int FLAG_SECURE = 0x1 << 1;
static final int FLAG_SCO = 0x1 << 2; static final int FLAG_SCO = 0x1 << 2;
static final int FLAG_BEACON = 0x1 << 3;
/** Flag requesting the use of an output stream supporting hardware A/V synchronization. */
public static final int FLAG_HW_AV_SYNC = 0x1 << 4;
static final int FLAG_HW_HOTWORD = 0x1 << 5;
static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1 << 6;
static final int FLAG_BYPASS_MUTE = 0x1 << 7;
static final int FLAG_LOW_LATENCY = 0x1 << 8;
static final int FLAG_DEEP_BUFFER = 0x1 << 9;
static final int FLAG_ALL =
(FLAG_AUDIBILITY_ENFORCED
| FLAG_SECURE
| FLAG_SCO
| FLAG_BEACON
| FLAG_HW_AV_SYNC
| FLAG_HW_HOTWORD
| FLAG_BYPASS_INTERRUPTION_POLICY
| FLAG_BYPASS_MUTE
| FLAG_LOW_LATENCY
| FLAG_DEEP_BUFFER);
static final int FLAG_ALL_PUBLIC =
(FLAG_AUDIBILITY_ENFORCED | FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY);
static final int INVALID_STREAM_TYPE = -1; // AudioSystem.STREAM_DEFAULT static final int INVALID_STREAM_TYPE = -1; // AudioSystem.STREAM_DEFAULT
public final AudioAttributesImpl mImpl; private final AudioAttributesImpl mImpl;
AudioAttributesCompat(AudioAttributesImpl impl) { AudioAttributesCompat(AudioAttributesImpl impl) {
mImpl = impl; mImpl = impl;
} }
/**
* Returns the stream type matching the given attributes for volume control. Use this method to
* derive the stream type needed to configure the volume control slider in an {@link
* android.app.Activity} with {@link android.app.Activity#setVolumeControlStream(int)}. <br>
* Do not use this method to set the stream type on an audio player object (e.g. {@link
* android.media.AudioTrack}, {@link android.media.MediaPlayer}) as this is deprecated; use <code>
* AudioAttributes</code> instead.
*
* @return a valid stream type for <code>Activity</code> or stream volume control that matches the
* attributes, or {@link AudioManager#USE_DEFAULT_STREAM_TYPE} if there isn't a direct match.
* Note that <code>USE_DEFAULT_STREAM_TYPE</code> is not a valid value for {@link
* AudioManager#setStreamVolume(int, int, int)}.
*/
public int getVolumeControlStream() {
return mImpl.getVolumeControlStream();
}
// public API unique to AudioAttributesCompat // public API unique to AudioAttributesCompat
/** /**
@ -301,17 +250,12 @@ public class AudioAttributesCompat {
* @param aa an instance of {@link AudioAttributes}. * @param aa an instance of {@link AudioAttributes}.
* @return the new <code>AudioAttributesCompat</code>, or <code>null</code> on API &lt; 21 * @return the new <code>AudioAttributesCompat</code>, or <code>null</code> on API &lt; 21
*/ */
@Nullable
public static AudioAttributesCompat wrap(Object aa) { public static AudioAttributesCompat wrap(Object aa) {
if (sForceLegacyBehavior) {
return null;
}
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
return new AudioAttributesCompat(new AudioAttributesImplApi26((AudioAttributes) aa)); return new AudioAttributesCompat(new AudioAttributesImplApi26((AudioAttributes) aa));
} else if (Build.VERSION.SDK_INT >= 21) { } else {
return new AudioAttributesCompat(new AudioAttributesImplApi21((AudioAttributes) aa)); return new AudioAttributesCompat(new AudioAttributesImplApi21((AudioAttributes) aa));
} }
return null;
} }
// The rest of this file implements an approximation to AudioAttributes using old stream types // The rest of this file implements an approximation to AudioAttributes using old stream types
@ -373,14 +317,10 @@ public class AudioAttributesCompat {
* default playback behavior in terms of routing and volume management. * default playback behavior in terms of routing and volume management.
*/ */
public Builder() { public Builder() {
if (sForceLegacyBehavior) { if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplBase.Builder();
} else if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplApi26.Builder(); mBuilderImpl = new AudioAttributesImplApi26.Builder();
} else if (Build.VERSION.SDK_INT >= 21) {
mBuilderImpl = new AudioAttributesImplApi21.Builder();
} else { } else {
mBuilderImpl = new AudioAttributesImplBase.Builder(); mBuilderImpl = new AudioAttributesImplApi21.Builder();
} }
} }
@ -390,14 +330,10 @@ public class AudioAttributesCompat {
* @param aa the AudioAttributesCompat object whose data will be reused in the new Builder. * @param aa the AudioAttributesCompat object whose data will be reused in the new Builder.
*/ */
public Builder(AudioAttributesCompat aa) { public Builder(AudioAttributesCompat aa) {
if (sForceLegacyBehavior) { if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplBase.Builder(aa);
} else if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplApi26.Builder(checkNotNull(aa.unwrap())); mBuilderImpl = new AudioAttributesImplApi26.Builder(checkNotNull(aa.unwrap()));
} else if (Build.VERSION.SDK_INT >= 21) {
mBuilderImpl = new AudioAttributesImplApi21.Builder(checkNotNull(aa.unwrap()));
} else { } else {
mBuilderImpl = new AudioAttributesImplBase.Builder(aa); mBuilderImpl = new AudioAttributesImplApi21.Builder(checkNotNull(aa.unwrap()));
} }
} }
@ -458,9 +394,8 @@ public class AudioAttributesCompat {
* *
* <p>This is a bitwise OR with the existing flags. * <p>This is a bitwise OR with the existing flags.
* *
* @param flags a combination of {@link AudioAttributesCompat#FLAG_AUDIBILITY_ENFORCED}, {@link * @param flags The optional flag of {@link AudioAttributesCompat#FLAG_AUDIBILITY_ENFORCED}.
* AudioAttributesCompat#FLAG_HW_AV_SYNC}. * @return The same Builder instance.
* @return the same Builder instance.
*/ */
public Builder setFlags(int flags) { public Builder setFlags(int flags) {
mBuilderImpl.setFlags(flags); mBuilderImpl.setFlags(flags);
@ -498,92 +433,31 @@ public class AudioAttributesCompat {
return mImpl.toString(); return mImpl.toString();
} }
static String usageToString(int usage) {
switch (usage) {
case USAGE_UNKNOWN:
return "USAGE_UNKNOWN";
case USAGE_MEDIA:
return "USAGE_MEDIA";
case USAGE_VOICE_COMMUNICATION:
return "USAGE_VOICE_COMMUNICATION";
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return "USAGE_VOICE_COMMUNICATION_SIGNALLING";
case USAGE_ALARM:
return "USAGE_ALARM";
case USAGE_NOTIFICATION:
return "USAGE_NOTIFICATION";
case USAGE_NOTIFICATION_RINGTONE:
return "USAGE_NOTIFICATION_RINGTONE";
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST";
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT";
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED";
case USAGE_NOTIFICATION_EVENT:
return "USAGE_NOTIFICATION_EVENT";
case USAGE_ASSISTANCE_ACCESSIBILITY:
return "USAGE_ASSISTANCE_ACCESSIBILITY";
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE";
case USAGE_ASSISTANCE_SONIFICATION:
return "USAGE_ASSISTANCE_SONIFICATION";
case USAGE_GAME:
return "USAGE_GAME";
case USAGE_ASSISTANT:
return "USAGE_ASSISTANT";
default:
return "unknown usage " + usage;
}
}
abstract static class AudioManagerHidden { abstract static class AudioManagerHidden {
public static final int STREAM_BLUETOOTH_SCO = 6; public static final int STREAM_BLUETOOTH_SCO = 6;
public static final int STREAM_SYSTEM_ENFORCED = 7; public static final int STREAM_SYSTEM_ENFORCED = 7;
public static final int STREAM_TTS = 9;
public static final int STREAM_ACCESSIBILITY = 10; public static final int STREAM_ACCESSIBILITY = 10;
private AudioManagerHidden() {} private AudioManagerHidden() {}
} }
/** Prevent AudioAttributes from being used even on platforms that support it. */ static int toVolumeStreamType(int flags, @AttributeUsage int usage) {
public static void setForceLegacyBehavior(boolean force) {
sForceLegacyBehavior = force;
}
int getRawLegacyStreamType() {
return mImpl.getRawLegacyStreamType();
}
static int toVolumeStreamType(
boolean fromGetVolumeControlStream, int flags, @AttributeUsage int usage) {
// flags to stream type mapping // flags to stream type mapping
if ((flags & FLAG_AUDIBILITY_ENFORCED) == FLAG_AUDIBILITY_ENFORCED) { if ((flags & FLAG_AUDIBILITY_ENFORCED) == FLAG_AUDIBILITY_ENFORCED) {
return fromGetVolumeControlStream return AudioManagerHidden.STREAM_SYSTEM_ENFORCED;
? AudioManager.STREAM_SYSTEM
: AudioManagerHidden.STREAM_SYSTEM_ENFORCED;
} }
if ((flags & FLAG_SCO) == FLAG_SCO) { if ((flags & FLAG_SCO) == FLAG_SCO) {
return fromGetVolumeControlStream return AudioManagerHidden.STREAM_BLUETOOTH_SCO;
? AudioManager.STREAM_VOICE_CALL
: AudioManagerHidden.STREAM_BLUETOOTH_SCO;
} }
// usage to stream type mapping // usage to stream type mapping
switch (usage) { switch (usage) {
case USAGE_MEDIA:
case USAGE_GAME:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANT:
return AudioManager.STREAM_MUSIC;
case USAGE_ASSISTANCE_SONIFICATION: case USAGE_ASSISTANCE_SONIFICATION:
return AudioManager.STREAM_SYSTEM; return AudioManager.STREAM_SYSTEM;
case USAGE_VOICE_COMMUNICATION: case USAGE_VOICE_COMMUNICATION:
return AudioManager.STREAM_VOICE_CALL; return AudioManager.STREAM_VOICE_CALL;
case USAGE_VOICE_COMMUNICATION_SIGNALLING: case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return fromGetVolumeControlStream return AudioManager.STREAM_DTMF;
? AudioManager.STREAM_VOICE_CALL
: AudioManager.STREAM_DTMF;
case USAGE_ALARM: case USAGE_ALARM:
return AudioManager.STREAM_ALARM; return AudioManager.STREAM_ALARM;
case USAGE_NOTIFICATION_RINGTONE: case USAGE_NOTIFICATION_RINGTONE:
@ -596,27 +470,17 @@ public class AudioAttributesCompat {
return AudioManager.STREAM_NOTIFICATION; return AudioManager.STREAM_NOTIFICATION;
case USAGE_ASSISTANCE_ACCESSIBILITY: case USAGE_ASSISTANCE_ACCESSIBILITY:
return AudioManagerHidden.STREAM_ACCESSIBILITY; return AudioManagerHidden.STREAM_ACCESSIBILITY;
case USAGE_UNKNOWN:
return AudioManager.STREAM_MUSIC;
default: default:
if (fromGetVolumeControlStream) {
throw new IllegalArgumentException(
"Unknown usage value " + usage + " in audio attributes");
} else {
return AudioManager.STREAM_MUSIC; return AudioManager.STREAM_MUSIC;
} }
} }
}
@Override @Override
public boolean equals(@Nullable Object o) { public boolean equals(@Nullable Object o) {
if (!(o instanceof AudioAttributesCompat)) { if (!(o instanceof AudioAttributesCompat)) {
return false; return false;
} }
final AudioAttributesCompat that = (AudioAttributesCompat) o; AudioAttributesCompat that = (AudioAttributesCompat) o;
if (this.mImpl == null) {
return that.mImpl == null;
}
return this.mImpl.equals(that.mImpl); return this.mImpl.equals(that.mImpl);
} }
@ -640,7 +504,7 @@ public class AudioAttributesCompat {
USAGE_VIRTUAL_SOURCE USAGE_VIRTUAL_SOURCE
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {} private @interface AttributeUsage {}
@IntDef({ @IntDef({
CONTENT_TYPE_UNKNOWN, CONTENT_TYPE_UNKNOWN,
@ -650,20 +514,15 @@ public class AudioAttributesCompat {
CONTENT_TYPE_SONIFICATION CONTENT_TYPE_SONIFICATION
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface AttributeContentType {} private @interface AttributeContentType {}
public interface AudioAttributesImpl { private interface AudioAttributesImpl {
/** Gets framework {@link android.media.AudioAttributes}. */ /** Gets framework {@link android.media.AudioAttributes}. */
@Nullable @Nullable
Object getAudioAttributes(); Object getAudioAttributes();
int getVolumeControlStream();
int getLegacyStreamType(); int getLegacyStreamType();
// Returns explicitly set legacy stream type.
int getRawLegacyStreamType();
int getContentType(); int getContentType();
@AudioAttributesCompat.AttributeUsage @AudioAttributesCompat.AttributeUsage
@ -685,272 +544,11 @@ public class AudioAttributesCompat {
} }
} }
public static class AudioAttributesImplBase implements AudioAttributesImpl { private static class AudioAttributesImplApi21 implements AudioAttributesImpl {
public int mUsage = USAGE_UNKNOWN;
public int mContentType = CONTENT_TYPE_UNKNOWN;
public int mFlags = 0x0;
public int mLegacyStream = INVALID_STREAM_TYPE;
public AudioAttributesImplBase() {}
AudioAttributesImplBase(int contentType, int flags, int usage, int legacyStream) {
mContentType = contentType;
mFlags = flags;
mUsage = usage;
mLegacyStream = legacyStream;
}
@Override
@Nullable
public Object getAudioAttributes() {
return null;
}
@Override
public int getVolumeControlStream() {
return AudioAttributesCompat.toVolumeStreamType(true, mFlags, mUsage);
}
@Override
public int getLegacyStreamType() {
if (mLegacyStream != INVALID_STREAM_TYPE) {
return mLegacyStream;
}
return AudioAttributesCompat.toVolumeStreamType(false, mFlags, mUsage);
}
@Override
public int getRawLegacyStreamType() {
return mLegacyStream;
}
@Override
public int getContentType() {
return mContentType;
}
@Override
public @AudioAttributesCompat.AttributeUsage int getUsage() {
return mUsage;
}
@Override
public int getFlags() {
int flags = mFlags;
int legacyStream = getLegacyStreamType();
if (legacyStream == AudioManagerHidden.STREAM_BLUETOOTH_SCO) {
flags |= AudioAttributesCompat.FLAG_SCO;
} else if (legacyStream == AudioManagerHidden.STREAM_SYSTEM_ENFORCED) {
flags |= AudioAttributesCompat.FLAG_AUDIBILITY_ENFORCED;
}
return flags & AudioAttributesCompat.FLAG_ALL_PUBLIC;
}
//////////////////////////////////////////////////////////////////////
// Override Object methods
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {mContentType, mFlags, mUsage, mLegacyStream});
}
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof AudioAttributesImplBase)) {
return false;
}
final AudioAttributesImplBase that = (AudioAttributesImplBase) o;
return ((mContentType == that.getContentType())
&& (mFlags == that.getFlags())
&& (mUsage == that.getUsage())
&& (mLegacyStream == that.mLegacyStream)); // query the slot directly, don't guess
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("AudioAttributesCompat:");
if (mLegacyStream != INVALID_STREAM_TYPE) {
sb.append(" stream=").append(mLegacyStream);
sb.append(" derived");
}
sb.append(" usage=")
.append(AudioAttributesCompat.usageToString(mUsage))
.append(" content=")
.append(mContentType)
.append(" flags=0x")
.append(Integer.toHexString(mFlags).toUpperCase(Locale.ROOT));
return sb.toString();
}
static class Builder implements AudioAttributesImpl.Builder {
private int mUsage = USAGE_UNKNOWN;
private int mContentType = CONTENT_TYPE_UNKNOWN;
private int mFlags = 0x0;
private int mLegacyStream = INVALID_STREAM_TYPE;
Builder() {}
Builder(AudioAttributesCompat aa) {
mUsage = aa.getUsage();
mContentType = aa.getContentType();
mFlags = aa.getFlags();
mLegacyStream = aa.getRawLegacyStreamType();
}
@Override
public AudioAttributesImpl build() {
return new AudioAttributesImplBase(mContentType, mFlags, mUsage, mLegacyStream);
}
@Override
public Builder setUsage(@AudioAttributesCompat.AttributeUsage int usage) {
switch (usage) {
case USAGE_UNKNOWN:
case USAGE_MEDIA:
case USAGE_VOICE_COMMUNICATION:
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
case USAGE_ALARM:
case USAGE_NOTIFICATION:
case USAGE_NOTIFICATION_RINGTONE:
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case USAGE_NOTIFICATION_EVENT:
case USAGE_ASSISTANCE_ACCESSIBILITY:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANCE_SONIFICATION:
case USAGE_GAME:
case USAGE_VIRTUAL_SOURCE:
mUsage = usage;
break;
// TODO: shouldn't it be USAGE_ASSISTANT?
case USAGE_ASSISTANT:
mUsage = USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
break;
default:
mUsage = USAGE_UNKNOWN;
}
return this;
}
@Override
public Builder setContentType(@AudioAttributesCompat.AttributeContentType int contentType) {
switch (contentType) {
case CONTENT_TYPE_UNKNOWN:
case CONTENT_TYPE_MOVIE:
case CONTENT_TYPE_MUSIC:
case CONTENT_TYPE_SONIFICATION:
case CONTENT_TYPE_SPEECH:
mContentType = contentType;
break;
default:
mContentType = CONTENT_TYPE_UNKNOWN;
}
return this;
}
@Override
public Builder setFlags(int flags) {
flags &= AudioAttributesCompat.FLAG_ALL;
mFlags |= flags;
return this;
}
@Override
public Builder setLegacyStreamType(int streamType) {
if (streamType == AudioManagerHidden.STREAM_ACCESSIBILITY) {
throw new IllegalArgumentException(
"STREAM_ACCESSIBILITY is not a legacy stream "
+ "type that was used for audio playback");
}
mLegacyStream = streamType;
return setInternalLegacyStreamType(streamType);
}
private Builder setInternalLegacyStreamType(int streamType) {
switch (streamType) {
case AudioManager.STREAM_VOICE_CALL:
mContentType = CONTENT_TYPE_SPEECH;
break;
case AudioManagerHidden.STREAM_SYSTEM_ENFORCED:
mFlags |= AudioAttributesCompat.FLAG_AUDIBILITY_ENFORCED;
// intended fall through, attributes in common with STREAM_SYSTEM
case AudioManager.STREAM_SYSTEM:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManager.STREAM_RING:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManager.STREAM_MUSIC:
mContentType = CONTENT_TYPE_MUSIC;
break;
case AudioManager.STREAM_ALARM:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManager.STREAM_NOTIFICATION:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManagerHidden.STREAM_BLUETOOTH_SCO:
mContentType = CONTENT_TYPE_SPEECH;
mFlags |= AudioAttributesCompat.FLAG_SCO;
break;
case AudioManager.STREAM_DTMF:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManagerHidden.STREAM_TTS:
mContentType = CONTENT_TYPE_SONIFICATION;
break;
case AudioManager.STREAM_ACCESSIBILITY:
mContentType = CONTENT_TYPE_SPEECH;
break;
default:
Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributesCompat");
}
mUsage = usageForStreamType(streamType);
return this;
}
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static int usageForStreamType(int streamType) {
switch (streamType) {
case AudioManager.STREAM_VOICE_CALL:
return USAGE_VOICE_COMMUNICATION;
case AudioManagerHidden.STREAM_SYSTEM_ENFORCED:
case AudioManager.STREAM_SYSTEM:
return USAGE_ASSISTANCE_SONIFICATION;
case AudioManager.STREAM_RING:
return USAGE_NOTIFICATION_RINGTONE;
case AudioManager.STREAM_MUSIC:
return USAGE_MEDIA;
case AudioManager.STREAM_ALARM:
return USAGE_ALARM;
case AudioManager.STREAM_NOTIFICATION:
return USAGE_NOTIFICATION;
case AudioManagerHidden.STREAM_BLUETOOTH_SCO:
return USAGE_VOICE_COMMUNICATION;
case AudioManager.STREAM_DTMF:
return USAGE_VOICE_COMMUNICATION_SIGNALLING;
case AudioManager.STREAM_ACCESSIBILITY:
return USAGE_ASSISTANCE_ACCESSIBILITY;
case AudioManagerHidden.STREAM_TTS:
default:
return USAGE_UNKNOWN;
}
}
}
@RequiresApi(21)
public static class AudioAttributesImplApi21 implements AudioAttributesImpl {
@Nullable public AudioAttributes mAudioAttributes; @Nullable public AudioAttributes mAudioAttributes;
public int mLegacyStreamType = INVALID_STREAM_TYPE; public final int mLegacyStreamType;
public AudioAttributesImplApi21() {}
AudioAttributesImplApi21(AudioAttributes audioAttributes) { AudioAttributesImplApi21(AudioAttributes audioAttributes) {
this(audioAttributes, INVALID_STREAM_TYPE); this(audioAttributes, INVALID_STREAM_TYPE);
@ -967,23 +565,12 @@ public class AudioAttributesCompat {
return mAudioAttributes; return mAudioAttributes;
} }
@Override
public int getVolumeControlStream() {
// TODO: address the framework change ag/4995785.
return AudioAttributesCompat.toVolumeStreamType(true, getFlags(), getUsage());
}
@Override @Override
public int getLegacyStreamType() { public int getLegacyStreamType() {
if (mLegacyStreamType != INVALID_STREAM_TYPE) { if (mLegacyStreamType != INVALID_STREAM_TYPE) {
return mLegacyStreamType; return mLegacyStreamType;
} }
return AudioAttributesCompat.toVolumeStreamType(false, getFlags(), getUsage()); return AudioAttributesCompat.toVolumeStreamType(getFlags(), getUsage());
}
@Override
public int getRawLegacyStreamType() {
return mLegacyStreamType;
} }
@Override @Override
@ -1020,7 +607,6 @@ public class AudioAttributesCompat {
return "AudioAttributesCompat: audioattributes=" + mAudioAttributes; return "AudioAttributesCompat: audioattributes=" + mAudioAttributes;
} }
@RequiresApi(21)
static class Builder implements AudioAttributesImpl.Builder { static class Builder implements AudioAttributesImpl.Builder {
final AudioAttributes.Builder mFwkBuilder; final AudioAttributes.Builder mFwkBuilder;
@ -1069,19 +655,12 @@ public class AudioAttributesCompat {
} }
@RequiresApi(26) @RequiresApi(26)
public static class AudioAttributesImplApi26 extends AudioAttributesImplApi21 { private static class AudioAttributesImplApi26 extends AudioAttributesImplApi21 {
public AudioAttributesImplApi26() {}
AudioAttributesImplApi26(AudioAttributes audioAttributes) { AudioAttributesImplApi26(AudioAttributes audioAttributes) {
super(audioAttributes, INVALID_STREAM_TYPE); super(audioAttributes, INVALID_STREAM_TYPE);
} }
@Override
public int getVolumeControlStream() {
return checkNotNull(mAudioAttributes).getVolumeControlStream();
}
@RequiresApi(26) @RequiresApi(26)
static class Builder extends AudioAttributesImplApi21.Builder { static class Builder extends AudioAttributesImplApi21.Builder {
Builder() { Builder() {

View File

@ -49,13 +49,6 @@ public class MediaBrowserProtocol {
public static final String EXTRA_MESSENGER_BINDER = "extra_messenger"; public static final String EXTRA_MESSENGER_BINDER = "extra_messenger";
public static final String EXTRA_SESSION_BINDER = "extra_session_binder"; public static final String EXTRA_SESSION_BINDER = "extra_session_binder";
/**
* MediaBrowserCompat will check the version of the connected MediaBrowserServiceCompat, and it
* will not send messages if they are introduced in the higher version of the
* MediaBrowserServiceCompat.
*/
public static final int SERVICE_VERSION_1 = 1;
/** /**
* To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to avoid * To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to avoid
* using framework code as much as possible (b/62648808). For backward compatibility, service v2 * using framework code as much as possible (b/62648808). For backward compatibility, service v2

View File

@ -60,7 +60,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.browse.MediaBrowser; import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -130,8 +129,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
static final String TAG = "MBServiceCompat"; static final String TAG = "MBServiceCompat";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final float EPSILON = 0.00001f;
private @MonotonicNonNull MediaBrowserServiceImpl mImpl; private @MonotonicNonNull MediaBrowserServiceImpl mImpl;
/** The {@link Intent} that must be declared as handled by the service. */ /** The {@link Intent} that must be declared as handled by the service. */
@ -176,7 +173,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@SuppressWarnings({ @SuppressWarnings({
"argument.type.incompatible", "argument.type.incompatible",
"assignment.type.incompatible" "assignment.type.incompatible"
}) // Using this before construtor completes }) // Using this before constructor completes
final ServiceHandler mHandler = new ServiceHandler(/* service= */ this); final ServiceHandler mHandler = new ServiceHandler(/* service= */ this);
@Nullable MediaSessionCompat.Token mSession; @Nullable MediaSessionCompat.Token mSession;
@ -199,115 +196,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
RemoteUserInfo getCurrentBrowserInfo(); RemoteUserInfo getCurrentBrowserInfo();
} }
class MediaBrowserServiceImplBase implements MediaBrowserServiceImpl {
private @MonotonicNonNull Messenger mMessenger;
@Override
public void onCreate() {
mMessenger = new Messenger(mHandler);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return checkNotNull(mMessenger).getBinder();
}
return null;
}
@Override
public void setSessionToken(final MediaSessionCompat.Token token) {
mHandler.post(
new Runnable() {
@Override
public void run() {
Iterator<ConnectionRecord> iter = mConnections.values().iterator();
while (iter.hasNext()) {
ConnectionRecord connection = iter.next();
try {
BrowserRoot root = checkNotNull(connection.root);
checkNotNull(connection.callbacks)
.onConnect(root.getRootId(), token, root.getExtras());
} catch (RemoteException e) {
Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
iter.remove();
}
}
}
});
}
@Override
public void notifyChildrenChanged(String parentId, @Nullable Bundle options) {
mHandler.post(
new Runnable() {
@Override
public void run() {
for (IBinder binder : mConnections.keySet()) {
ConnectionRecord connection = checkNotNull(mConnections.get(binder));
notifyChildrenChangedOnHandler(connection, parentId, options);
}
}
});
}
@Override
public void notifyChildrenChanged(
final RemoteUserInfo remoteUserInfo, final String parentId, final Bundle options) {
mHandler.post(
new Runnable() {
@Override
public void run() {
for (int i = 0; i < mConnections.size(); i++) {
ConnectionRecord connection = mConnections.valueAt(i);
if (connection.browserInfo.equals(remoteUserInfo)) {
notifyChildrenChangedOnHandler(connection, parentId, options);
break;
}
}
}
});
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void notifyChildrenChangedOnHandler(
ConnectionRecord connection, String parentId, @Nullable Bundle options) {
List<Pair<@NullableType IBinder, @NullableType Bundle>> callbackList =
connection.subscriptions.get(parentId);
if (callbackList != null) {
for (Pair<@NullableType IBinder, @NullableType Bundle> callback : callbackList) {
if (MediaBrowserCompatUtils.hasDuplicatedItems(options, callback.second)) {
performLoadChildren(parentId, connection, callback.second, options);
}
}
}
// Don't break, because multiple remoteUserInfo may match.
}
@Nullable
@Override
public Bundle getBrowserRootHints() {
if (mCurConnection == null) {
throw new IllegalStateException(
"This should be called inside of onLoadChildren,"
+ " onLoadItem, onSearch, or onCustomAction methods");
}
return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints);
}
@Override
public RemoteUserInfo getCurrentBrowserInfo() {
if (mCurConnection == null) {
throw new IllegalStateException(
"This should be called inside of onLoadChildren,"
+ " onLoadItem, onSearch, or onCustomAction methods");
}
return mCurConnection.browserInfo;
}
}
@RequiresApi(21)
class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl { class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl {
final List<Bundle> mRootExtrasList = new ArrayList<>(); final List<Bundle> mRootExtrasList = new ArrayList<>();
@MonotonicNonNull MediaBrowserService mServiceFwk; @MonotonicNonNull MediaBrowserService mServiceFwk;
@ -345,8 +233,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
} }
mRootExtrasList.clear(); mRootExtrasList.clear();
} }
checkNotNull(mServiceFwk) checkNotNull(mServiceFwk).setSessionToken(token.getToken());
.setSessionToken(checkNotNull((MediaSession.Token) token.getToken()));
} }
@Override @Override
@ -520,7 +407,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
return mCurConnection.browserInfo; return mCurConnection.browserInfo;
} }
@RequiresApi(21)
class MediaBrowserServiceApi21 extends MediaBrowserService { class MediaBrowserServiceApi21 extends MediaBrowserService {
@SuppressWarnings("method.invocation.invalid") // Calling base method from constructor @SuppressWarnings("method.invocation.invalid") // Calling base method from constructor
MediaBrowserServiceApi21(Context context) { MediaBrowserServiceApi21(Context context) {
@ -543,8 +429,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override @Override
public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) { public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
MediaBrowserServiceImplApi21.this.onLoadChildren( MediaBrowserServiceImplApi21.this.onLoadChildren(parentId, new ResultWrapper<>(result));
parentId, new ResultWrapper<List<Parcel>>(result));
} }
} }
} }
@ -588,7 +473,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override @Override
public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) { public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
MediaBrowserServiceImplApi23.this.onLoadItem(itemId, new ResultWrapper<Parcel>(result)); MediaBrowserServiceImplApi23.this.onLoadItem(itemId, new ResultWrapper<>(result));
} }
} }
} }
@ -672,7 +557,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
MediaSessionCompat.ensureClassLoader(options); MediaSessionCompat.ensureClassLoader(options);
mCurConnection = mConnectionFromFwk; mCurConnection = mConnectionFromFwk;
MediaBrowserServiceImplApi26.this.onLoadChildren( MediaBrowserServiceImplApi26.this.onLoadChildren(
parentId, new ResultWrapper<List<Parcel>>(result), options); parentId, new ResultWrapper<>(result), options);
mCurConnection = null; mCurConnection = null;
} }
} }
@ -794,10 +679,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
* #sendError} when they are done. If {@link #sendResult}, {@link #sendError}, or {@link #detach} * #sendError} when they are done. If {@link #sendResult}, {@link #sendError}, or {@link #detach}
* is called twice, an exception will be thrown. * is called twice, an exception will be thrown.
* *
* <p>Those functions might also want to call {@link #sendProgressUpdate} to send interim updates
* to the caller. If it is called after calling {@link #sendResult} or {@link #sendError}, an
* exception will be thrown.
*
* @see MediaBrowserServiceCompat#onLoadChildren * @see MediaBrowserServiceCompat#onLoadChildren
* @see MediaBrowserServiceCompat#onLoadItem * @see MediaBrowserServiceCompat#onLoadItem
* @see MediaBrowserServiceCompat#onSearch * @see MediaBrowserServiceCompat#onSearch
@ -826,23 +707,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
onResultSent(result); onResultSent(result);
} }
/**
* Send an interim update to the caller. This method is supported only when it is used in {@link
* #onCustomAction}.
*
* @param extras A bundle that contains extra data.
*/
public void sendProgressUpdate(@Nullable Bundle extras) {
if (mSendResultCalled || mSendErrorCalled) {
throw new IllegalStateException(
"sendProgressUpdate() called when either "
+ "sendResult() or sendError() had already been called for: "
+ mDebug);
}
checkExtraFields(extras);
onProgressUpdateSent(extras);
}
/** /**
* Notify the caller of a failure. This is supported only when it is used in {@link * Notify the caller of a failure. This is supported only when it is used in {@link
* #onCustomAction}. * #onCustomAction}.
@ -897,32 +761,12 @@ public abstract class MediaBrowserServiceCompat extends Service {
*/ */
void onResultSent(@Nullable T result) {} void onResultSent(@Nullable T result) {}
/** Called when an interim update is sent. */
void onProgressUpdateSent(@Nullable Bundle extras) {
throw new UnsupportedOperationException(
"It is not supported to send an interim update " + "for " + mDebug);
}
/** /**
* Called when an error is sent, after assertions about not being called twice have happened. * Called when an error is sent, after assertions about not being called twice have happened.
*/ */
void onErrorSent(@Nullable Bundle extras) { void onErrorSent(@Nullable Bundle extras) {
throw new UnsupportedOperationException("It is not supported to send an error for " + mDebug); throw new UnsupportedOperationException("It is not supported to send an error for " + mDebug);
} }
private void checkExtraFields(@Nullable Bundle extras) {
if (extras == null) {
return;
}
if (extras.containsKey(MediaBrowserCompat.EXTRA_DOWNLOAD_PROGRESS)) {
float value = extras.getFloat(MediaBrowserCompat.EXTRA_DOWNLOAD_PROGRESS);
if (value < -EPSILON || value > 1.0f + EPSILON) {
throw new IllegalArgumentException(
"The value of the EXTRA_DOWNLOAD_PROGRESS "
+ "field must be a float number within [0.0, 1.0]");
}
}
}
} }
private class ServiceBinderImpl { private class ServiceBinderImpl {
@ -968,7 +812,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
mConnections.put(b, connection); mConnections.put(b, connection);
b.linkToDeath(connection, 0); b.linkToDeath(connection, 0);
if (mSession != null) { if (mSession != null) {
callbacks.onConnect(root.getRootId(), mSession, root.getExtras()); callbacks.onConnect(root.getRootId(), checkNotNull(mSession), root.getExtras());
} }
} catch (RemoteException ex) { } catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. " + "pkg=" + pkg); Log.w(TAG, "Calling onConnect() failed. Dropping client. " + "pkg=" + pkg);
@ -1188,7 +1032,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
private interface ServiceCallbacks { private interface ServiceCallbacks {
IBinder asBinder(); IBinder asBinder();
void onConnect(String root, @Nullable MediaSessionCompat.Token session, @Nullable Bundle extras) void onConnect(String root, MediaSessionCompat.Token session, @Nullable Bundle extras)
throws RemoteException; throws RemoteException;
void onConnectFailed() throws RemoteException; void onConnectFailed() throws RemoteException;
@ -1214,8 +1058,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
} }
@Override @Override
public void onConnect( public void onConnect(String root, MediaSessionCompat.Token session, @Nullable Bundle extras)
String root, @Nullable MediaSessionCompat.Token session, @Nullable Bundle extras)
throws RemoteException { throws RemoteException {
if (extras == null) { if (extras == null) {
extras = new Bundle(); extras = new Bundle();
@ -1236,7 +1079,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null); sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null);
} }
@SuppressWarnings({"rawtypes", "unchecked"})
@Override @Override
public void onLoadChildren( public void onLoadChildren(
@Nullable String mediaId, @Nullable String mediaId,
@ -1268,7 +1110,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
} }
} }
@RequiresApi(21)
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
static class ResultWrapper<T> { static class ResultWrapper<T> {
MediaBrowserService.Result mResultFwk; MediaBrowserService.Result mResultFwk;
@ -1331,10 +1172,8 @@ public abstract class MediaBrowserServiceCompat extends Service {
mImpl = new MediaBrowserServiceImplApi26(); mImpl = new MediaBrowserServiceImplApi26();
} else if (Build.VERSION.SDK_INT >= 23) { } else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23(); mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();
} else { } else {
mImpl = new MediaBrowserServiceImplBase(); mImpl = new MediaBrowserServiceImplApi21();
} }
mImpl.onCreate(); mImpl.onCreate();
} }
@ -1437,7 +1276,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
/** /**
* Called when a {@link MediaBrowserCompat#unsubscribe} is called. * Called when a {@link MediaBrowserCompat#unsubscribe} is called.
* *
* @param id * @param id The id to unsubscribe.
*/ */
public void onUnsubscribe(@Nullable String id) {} public void onUnsubscribe(@Nullable String id) {}
@ -1494,8 +1333,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
* <p>Implementations must call either {@link Result#sendResult} or {@link Result#sendError}. If * <p>Implementations must call either {@link Result#sendResult} or {@link Result#sendError}. If
* the requested custom action will be an expensive operation {@link Result#detach} may be called * the requested custom action will be an expensive operation {@link Result#detach} may be called
* before returning from this function, and then the service can send the result later when the * before returning from this function, and then the service can send the result later when the
* custom action is completed. Implementation can also call {@link Result#sendProgressUpdate} to * custom action is completed.
* send an interim update to the requester.
* *
* <p>If the requested custom action is not supported by this service, call {@link * <p>If the requested custom action is not supported by this service, call {@link
* Result#sendError}. The default implementation will invoke {@link Result#sendError}. * Result#sendError}. The default implementation will invoke {@link Result#sendError}.
@ -1961,11 +1799,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
receiver.send(RESULT_OK, result); receiver.send(RESULT_OK, result);
} }
@Override
void onProgressUpdateSent(@Nullable Bundle data) {
receiver.send(RESULT_PROGRESS_UPDATE, data);
}
@Override @Override
void onErrorSent(@Nullable Bundle data) { void onErrorSent(@Nullable Bundle data) {
receiver.send(RESULT_ERROR, data); receiver.send(RESULT_ERROR, data);

View File

@ -19,7 +19,6 @@ import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.ForegroundServiceStartNotAllowedException; import android.app.ForegroundServiceStartNotAllowedException;
import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
@ -35,7 +34,6 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.PlaybackStateCompat.MediaKeyAction;
import java.util.List; import java.util.List;
/** /**
@ -74,22 +72,6 @@ import java.util.List;
* &lt;/service&gt; * &lt;/service&gt;
* </pre> * </pre>
* *
* Events can then be handled in {@link Service#onStartCommand(Intent, int, int)} by calling {@link
* MediaButtonReceiver#handleIntent(MediaSessionCompat, Intent)}, passing in your current {@link
* MediaSessionCompat}:
*
* <pre>
* private MediaSessionCompat mMediaSessionCompat = ...;
*
* public int onStartCommand(Intent intent, int flags, int startId) {
* MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent);
* return super.onStartCommand(intent, flags, startId);
* }
* </pre>
*
* This ensures that the correct callbacks to {@link MediaSessionCompat.Callback} will be triggered
* based on the incoming {@link KeyEvent}.
*
* <p class="note"><strong>Note:</strong> Once the service is started, it must start to run in the * <p class="note"><strong>Note:</strong> Once the service is started, it must start to run in the
* foreground. * foreground.
* *
@ -122,7 +104,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
if (Build.VERSION.SDK_INT >= 31 if (Build.VERSION.SDK_INT >= 31
&& Api31.instanceOfForegroundServiceStartNotAllowedException(e)) { && Api31.instanceOfForegroundServiceStartNotAllowedException(e)) {
onForegroundServiceStartNotAllowedException( onForegroundServiceStartNotAllowedException(
intent, Api31.castToForegroundServiceStartNotAllowedException(e)); Api31.castToForegroundServiceStartNotAllowedException(e));
} else { } else {
throw e; throw e;
} }
@ -171,13 +153,11 @@ public class MediaButtonReceiver extends BroadcastReceiver {
* <p>In all other cases, apps should use a {@linkplain MediaBrowserCompat media browser} to bind * <p>In all other cases, apps should use a {@linkplain MediaBrowserCompat media browser} to bind
* to and start the service instead of broadcasting an intent. * to and start the service instead of broadcasting an intent.
* *
* @param intent The intent that was used {@linkplain Context#startForegroundService(Intent) for
* starting the foreground service}.
* @param e The exception thrown by the system and caught by this broadcast receiver. * @param e The exception thrown by the system and caught by this broadcast receiver.
*/ */
@RequiresApi(31) @RequiresApi(31)
protected void onForegroundServiceStartNotAllowedException( protected void onForegroundServiceStartNotAllowedException(
Intent intent, ForegroundServiceStartNotAllowedException e) { ForegroundServiceStartNotAllowedException e) {
Log.e( Log.e(
TAG, TAG,
"caught exception when trying to start a foreground service from the " "caught exception when trying to start a foreground service from the "
@ -202,7 +182,6 @@ public class MediaButtonReceiver extends BroadcastReceiver {
mMediaBrowser = mediaBrowser; mMediaBrowser = mediaBrowser;
} }
@SuppressWarnings("deprecation")
@Override @Override
public void onConnected() { public void onConnected() {
MediaControllerCompat mediaController = MediaControllerCompat mediaController =
@ -227,112 +206,6 @@ public class MediaButtonReceiver extends BroadcastReceiver {
mPendingResult.finish(); mPendingResult.finish();
} }
} }
;
/**
* Extracts any available {@link KeyEvent} from an {@link Intent#ACTION_MEDIA_BUTTON} intent,
* passing it onto the {@link MediaSessionCompat} using {@link
* MediaControllerCompat#dispatchMediaButtonEvent(KeyEvent)}, which in turn will trigger callbacks
* to the {@link MediaSessionCompat.Callback} registered via {@link
* MediaSessionCompat#setCallback(MediaSessionCompat.Callback)}.
*
* @param mediaSessionCompat A {@link MediaSessionCompat} that has a {@link
* MediaSessionCompat.Callback} set.
* @param intent The intent to parse.
* @return The extracted {@link KeyEvent} if found, or null.
*/
@Nullable
@SuppressWarnings("deprecation")
public static KeyEvent handleIntent(MediaSessionCompat mediaSessionCompat, Intent intent) {
if (mediaSessionCompat == null
|| intent == null
|| !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|| !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
return null;
}
KeyEvent ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
MediaControllerCompat mediaController = mediaSessionCompat.getController();
mediaController.dispatchMediaButtonEvent(ke);
return ke;
}
/**
* Creates a broadcast pending intent that will send a media button event. The {@code action} will
* be translated to the appropriate {@link KeyEvent}, and it will be sent to the registered media
* button receiver in the given context. The {@code action} should be one of the following:
*
* <ul>
* <li>{@link PlaybackStateCompat#ACTION_PLAY}
* <li>{@link PlaybackStateCompat#ACTION_PAUSE}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}
* <li>{@link PlaybackStateCompat#ACTION_STOP}
* <li>{@link PlaybackStateCompat#ACTION_FAST_FORWARD}
* <li>{@link PlaybackStateCompat#ACTION_REWIND}
* <li>{@link PlaybackStateCompat#ACTION_PLAY_PAUSE}
* </ul>
*
* @param context The context of the application.
* @param action The action to be sent via the pending intent.
* @return Created pending intent, or null if cannot find a unique registered media button
* receiver or if the {@code action} is unsupported/invalid.
*/
@Nullable
public static PendingIntent buildMediaButtonPendingIntent(
Context context, @MediaKeyAction long action) {
ComponentName mbrComponent = getMediaButtonReceiverComponent(context);
if (mbrComponent == null) {
Log.w(
TAG,
"A unique media button receiver could not be found in the given context, so "
+ "couldn't build a pending intent.");
return null;
}
return buildMediaButtonPendingIntent(context, mbrComponent, action);
}
/**
* Creates a broadcast pending intent that will send a media button event. The {@code action} will
* be translated to the appropriate {@link KeyEvent}, and sent to the provided media button
* receiver via the pending intent. The {@code action} should be one of the following:
*
* <ul>
* <li>{@link PlaybackStateCompat#ACTION_PLAY}
* <li>{@link PlaybackStateCompat#ACTION_PAUSE}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}
* <li>{@link PlaybackStateCompat#ACTION_STOP}
* <li>{@link PlaybackStateCompat#ACTION_FAST_FORWARD}
* <li>{@link PlaybackStateCompat#ACTION_REWIND}
* <li>{@link PlaybackStateCompat#ACTION_PLAY_PAUSE}
* </ul>
*
* @param context The context of the application.
* @param mbrComponent The full component name of a media button receiver where you want to send
* this intent.
* @param action The action to be sent via the pending intent.
* @return Created pending intent, or null if the given component name is null or the {@code
* action} is unsupported/invalid.
*/
@Nullable
public static PendingIntent buildMediaButtonPendingIntent(
Context context, ComponentName mbrComponent, @MediaKeyAction long action) {
if (mbrComponent == null) {
Log.w(TAG, "The component name of media button receiver should be provided.");
return null;
}
int keyCode = PlaybackStateCompat.toKeyCode(action);
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
Log.w(TAG, "Cannot build a media button pending intent with the given action: " + action);
return null;
}
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.setComponent(mbrComponent);
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
return PendingIntent.getBroadcast(
context, keyCode, intent, Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0);
}
/** */ /** */
@Nullable @Nullable

View File

@ -26,7 +26,6 @@ import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.service.media.MediaBrowserService;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaBrowserCompat.ConnectionCallback; import androidx.media3.session.legacy.MediaBrowserCompat.ConnectionCallback;
@ -36,43 +35,6 @@ import java.util.ArrayList;
@UnstableApi @UnstableApi
@RestrictTo(LIBRARY) @RestrictTo(LIBRARY)
public final class MediaConstants { public final class MediaConstants {
/**
* Bundle key used for the account name in {@link MediaSessionCompat session} extras.
*
* <p>TYPE: String
*
* @see MediaControllerCompat#getExtras
* @see MediaSessionCompat#setExtras
*/
@SuppressLint("IntentName")
public static final String SESSION_EXTRAS_KEY_ACCOUNT_NAME =
"androidx.media.MediaSessionCompat.Extras.KEY_ACCOUNT_NAME";
/**
* Bundle key used for the account type in {@link MediaSessionCompat session} extras. The value
* would vary across media applications.
*
* <p>TYPE: String
*
* @see MediaControllerCompat#getExtras
* @see MediaSessionCompat#setExtras
*/
@SuppressLint("IntentName")
public static final String SESSION_EXTRAS_KEY_ACCOUNT_TYPE =
"androidx.media.MediaSessionCompat.Extras.KEY_ACCOUNT_TYPE";
/**
* Bundle key used for the account auth token value in {@link MediaSessionCompat session} extras.
* The value would vary across media applications.
*
* <p>TYPE: byte[]
*
* @see MediaControllerCompat#getExtras
* @see MediaSessionCompat#setExtras
*/
@SuppressLint("IntentName")
public static final String SESSION_EXTRAS_KEY_AUTHTOKEN =
"androidx.media.MediaSessionCompat.Extras.KEY_AUTHTOKEN";
/** /**
* Bundle key passed from {@link MediaSessionCompat} to the hosting {@link MediaControllerCompat} * Bundle key passed from {@link MediaSessionCompat} to the hosting {@link MediaControllerCompat}
@ -106,78 +68,12 @@ public final class MediaConstants {
public static final String SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV = public static final String SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV =
"android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
/**
* Bundle key used for media content id in {@link MediaMetadataCompat metadata}, should contain
* the same ID provided to <a href="https://developers.google.com/actions/media">Media Actions
* Catalog</a> in reference to this title (e.g., episode, movie). This key can contain the content
* ID of the currently playing episode or movie and can be used to help users continue watching
* after this session is paused or stopped.
*
* <p>TYPE: String
*
* @see MediaMetadataCompat
*/
@SuppressLint("IntentName")
public static final String METADATA_KEY_CONTENT_ID =
"androidx.media.MediaMetadatCompat.METADATA_KEY_CONTENT_ID";
/**
* Bundle key used for next episode's media content ID in {@link MediaMetadataCompat metadata},
* following the same ID and format provided to <a
* href="https://developers.google.com/actions/media">Media Actions Catalog</a> in reference to
* the next episode of the current title episode. This key can contain the content ID of the
* episode immediately following the currently playing episode and can be used to help users
* continue watching after this episode is over. This value is only valid for TV Episode content
* type and should be left blank for other content.
*
* <p>TYPE: String
*
* @see MediaMetadataCompat
*/
@SuppressLint("IntentName")
public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID =
"androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
/**
* Bundle key used for the TV series's media content ID in {@link MediaMetadataCompat metadata},
* following the same ID and format provided to <a
* href="https://developers.google.com/actions/media">Media Actions Catalog</a> in reference to
* the TV series of the current title episode. This value is only valid for TV Episode content
* type and should be left blank for other content.
*
* <p>TYPE: String
*
* @see MediaMetadataCompat
*/
@SuppressLint("IntentName")
public static final String METADATA_KEY_SERIES_CONTENT_ID =
"androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
/**
* Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the
* {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat} to
* indicate that the corresponding {@link MediaMetadataCompat} or {@link
* MediaBrowserCompat.MediaItem} has explicit content (i.e. user discretion is advised when
* viewing or listening to this content).
*
* <p>TYPE: long (to enable, use value {@link #METADATA_VALUE_ATTRIBUTE_PRESENT})
*
* @see MediaMetadataCompat#getLong(String)
* @see MediaMetadataCompat.Builder#putLong(String, long)
* @see MediaDescriptionCompat#getExtras()
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
*/
@SuppressLint("IntentName")
public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
/** /**
* Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the * Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the
* {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat} to * {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat} to
* indicate that the corresponding {@link MediaMetadataCompat} or {@link * indicate that the corresponding {@link MediaMetadataCompat} or {@link
* MediaBrowserCompat.MediaItem} is an advertisement. * MediaBrowserCompat.MediaItem} is an advertisement.
* *
* <p>TYPE: long (to enable, use value {@link #METADATA_VALUE_ATTRIBUTE_PRESENT})
*
* @see MediaMetadataCompat#getLong(String) * @see MediaMetadataCompat#getLong(String)
* @see MediaMetadataCompat.Builder#putLong(String, long) * @see MediaMetadataCompat.Builder#putLong(String, long)
* @see MediaDescriptionCompat#getExtras() * @see MediaDescriptionCompat#getExtras()
@ -186,16 +82,6 @@ public final class MediaConstants {
@SuppressLint("IntentName") @SuppressLint("IntentName")
public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT"; public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
/**
* Value sent through a key-value mapping of {@link MediaMetadataCompat}, or through {@link
* Bundle} extras on a different data type, to indicate the presence of an attribute described by
* its corresponding key.
*
* @see MediaMetadataCompat#getLong(String)
* @see MediaMetadataCompat.Builder#putLong(String, long)
*/
public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L;
/** /**
* Bundle key passed through root hints to the {@link MediaBrowserServiceCompat} to indicate the * Bundle key passed through root hints to the {@link MediaBrowserServiceCompat} to indicate the
* maximum number of children of the root node that can be supported by the hosting {@link * maximum number of children of the root node that can be supported by the hosting {@link
@ -288,28 +174,6 @@ public final class MediaConstants {
public static final String BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED = public static final String BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED =
"android.media.browse.SEARCH_SUPPORTED"; "android.media.browse.SEARCH_SUPPORTED";
/**
* Bundle key used to pass a browseable {@link android.media.browse.MediaBrowser.MediaItem} that
* represents 'Favorite' content or some other notion of preset/pinned content.
*
* <p>Use this key to indicate to consumers (e.g. Auto and Automotive) that they can display
* and/or subscribe to this item.
*
* <p>When this item is subscribed to, it is expected that the {@link MediaBrowserService} or
* {@link MediaBrowserServiceCompat} loads content that the user has marked for easy or quick
* access - e.g. favorite radio stations, pinned playlists, etc.
*
* <p>TYPE: MediaBrowser.MediaItem - note this should not be a {@link
* MediaBrowserCompat.MediaItem}
*
* @see MediaBrowserCompat#getExtras()
* @see MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)
* @see MediaBrowserServiceCompat.BrowserRoot#BrowserRoot(String, Bundle)
*/
@SuppressLint("IntentName")
public static final String BROWSER_SERVICE_EXTRAS_KEY_FAVORITES_MEDIA_ITEM =
"androidx.media.BrowserRoot.Extras.FAVORITES_MEDIA_ITEM";
/** /**
* Bundle key passed from the {@link MediaBrowserServiceCompat} to the hosting {@link * Bundle key passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
* MediaBrowserCompat} to indicate a preference about how playable instances of {@link * MediaBrowserCompat} to indicate a preference about how playable instances of {@link
@ -687,111 +551,6 @@ public final class MediaConstants {
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID =
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID"; "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
/**
* {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to indicate which browse
* node should be displayed next.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key in
* the {@link MediaBrowserServiceCompat.Result} passed in {@link
* MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
*
* <p>If this key is present in a {@link MediaBrowserCompat.CustomActionCallback} data {@link
* Bundle} the {@link MediaBrowserCompat} will update the current browse node when {@link
* MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by the
* {@link MediaBrowserServiceCompat}. The new browse node will be fetched by {@link
* MediaBrowserCompat#getItem(String, MediaBrowserCompat.ItemCallback)}.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions must implement
* {@link MediaBrowserServiceCompat#onLoadItem(String, MediaBrowserServiceCompat.Result)} to use
* this feature.
*
* <p>TYPE: string, string {@link MediaBrowserCompat.MediaItem} ID to set as new browse node.
*
* @see MediaBrowserCompat#sendCustomAction(String, Bundle,
* MediaBrowserCompat.CustomActionCallback)
* @see MediaBrowserCompat.CustomActionCallback
* @see MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
*/
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE =
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
/**
* {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to show the currently
* playing item.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key in
* the {@link MediaBrowserServiceCompat.Result} passed in {@link
* MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
*
* <p>If this key is present and the value is true in {@link
* MediaBrowserCompat.CustomActionCallback} {@link MediaBrowserServiceCompat.Result}, the
* currently playing item will be shown when {@link
* MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by the
* {@link MediaBrowserServiceCompat}.
*
* <p>TYPE: boolean, boolean value of true will show currently playing item.
*
* @see MediaBrowserCompat#sendCustomAction(String, Bundle,
* MediaBrowserCompat.CustomActionCallback)
* @see MediaBrowserCompat.CustomActionCallback
* @see MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
*/
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM =
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
/**
* {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to refresh a {@link
* MediaBrowserCompat.MediaItem} in the browse tree.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key in
* the {@link MediaBrowserServiceCompat.Result} passed in {@link
* MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
*
* <p>If this key is present in {@link MediaBrowserCompat.CustomActionCallback} {@link
* MediaBrowserServiceCompat.Result}, the item will be refreshed with {@link
* MediaBrowserCompat#getItem(String, MediaBrowserCompat.ItemCallback)} when {@link
* MediaBrowserCompat.CustomActionCallback#onProgressUpdate(String, Bundle, Bundle)} or {@link
* MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by the
* {@link MediaBrowserServiceCompat}.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions must implement
* {@link MediaBrowserServiceCompat#onLoadItem(String, MediaBrowserServiceCompat.Result)} in order
* to update the state of the item.
*
* <p>TYPE: string, string {@link MediaBrowserCompat.MediaItem} ID to refresh.
*
* @see MediaBrowserCompat#sendCustomAction(String, Bundle,
* MediaBrowserCompat.CustomActionCallback)
* @see MediaBrowserCompat.CustomActionCallback
* @see MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
*/
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM =
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
/**
* {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to set a message for the
* user.
*
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key in
* the {@link MediaBrowserServiceCompat.Result} passed in {@link
* MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
*
* <p>If this key is present in {@link MediaBrowserCompat.CustomActionCallback} {@link
* MediaBrowserServiceCompat.Result}, the message will be shown to the user when {@link
* MediaBrowserCompat.CustomActionCallback#onProgressUpdate(String, Bundle, Bundle)} or {@link
* MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by the
* {@link MediaBrowserServiceCompat}.
*
* <p>TYPE: string, localized message string to show the user.
*
* @see MediaBrowserCompat#sendCustomAction(String, Bundle,
* MediaBrowserCompat.CustomActionCallback)
* @see MediaBrowserCompat.CustomActionCallback
* @see MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
*/
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE =
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
/** /**
* Bundle key used for the media ID in {@link PlaybackStateCompat playback state} extras. It's for * Bundle key used for the media ID in {@link PlaybackStateCompat playback state} extras. It's for
* associating the playback state with the media being played so the value is expected to be same * associating the playback state with the media being played so the value is expected to be same
@ -897,30 +656,5 @@ public final class MediaConstants {
public static final String TRANSPORT_CONTROLS_EXTRAS_KEY_LEGACY_STREAM_TYPE = public static final String TRANSPORT_CONTROLS_EXTRAS_KEY_LEGACY_STREAM_TYPE =
"android.media.session.extra.LEGACY_STREAM_TYPE"; "android.media.session.extra.LEGACY_STREAM_TYPE";
/**
* Bundle key passed through the {@code extras} of {@link
* MediaControllerCompat.TransportControls#prepareFromMediaId(String, Bundle)}, {@link
* MediaControllerCompat.TransportControls#prepareFromSearch(String, Bundle)}, {@link
* MediaControllerCompat.TransportControls#prepareFromUri(Uri, Bundle)}, {@link
* MediaControllerCompat.TransportControls#playFromMediaId(String, Bundle)}, {@link
* MediaControllerCompat.TransportControls#playFromSearch(String, Bundle)}, or {@link
* MediaControllerCompat.TransportControls#playFromUri(Uri, Bundle)} to indicate whether the
* session should shuffle the media to be played or not. The extra parameter is limited to the
* current request and doesn't affect the {@link MediaSessionCompat#setShuffleMode(int) shuffle
* mode}.
*
* <p>TYPE: boolean
*
* @see MediaControllerCompat.TransportControls#prepareFromMediaId(String, Bundle)
* @see MediaControllerCompat.TransportControls#prepareFromSearch(String, Bundle)
* @see MediaControllerCompat.TransportControls#prepareFromUri(Uri, Bundle)
* @see MediaControllerCompat.TransportControls#playFromMediaId(String, Bundle)
* @see MediaControllerCompat.TransportControls#playFromSearch(String, Bundle)
* @see MediaControllerCompat.TransportControls#playFromUri(Uri, Bundle)
*/
@SuppressLint("IntentName")
public static final String TRANSPORT_CONTROLS_EXTRAS_KEY_SHUFFLE =
"androidx.media.MediaControllerCompat.TransportControls.extras.KEY_SHUFFLE";
private MediaConstants() {} private MediaConstants() {}
} }

View File

@ -18,7 +18,6 @@ package androidx.media3.session.legacy;
import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
@ -44,11 +43,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.R;
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem; import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
import androidx.media3.session.legacy.PlaybackStateCompat.CustomAction; import androidx.media3.session.legacy.PlaybackStateCompat.CustomAction;
import androidx.versionedparcelable.ParcelUtils; import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -124,50 +121,6 @@ public final class MediaControllerCompat {
public static final String COMMAND_ARGUMENT_INDEX = public static final String COMMAND_ARGUMENT_INDEX =
"android.support.v4.media.session.command.ARGUMENT_INDEX"; "android.support.v4.media.session.command.ARGUMENT_INDEX";
/**
* Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via {@link
* #getMediaController(Activity)}.
*
* <p>On API 21 and later, {@link Activity#setMediaController(MediaController)} will also be
* called.
*
* @param activity The activity to set the {@code mediaController} in, must not be null.
* @param mediaController The controller for the session which should receive media keys and
* volume changes on API 21 and later.
* @see #getMediaController(Activity)
* @see Activity#setMediaController(android.media.session.MediaController)
*/
public static void setMediaController(Activity activity, MediaControllerCompat mediaController) {
activity
.getWindow()
.getDecorView()
.setTag(R.id.media_controller_compat_view_tag, mediaController);
if (android.os.Build.VERSION.SDK_INT >= 21) {
MediaControllerImplApi21.setMediaController(activity, mediaController);
}
}
/**
* Retrieves the {@link MediaControllerCompat} set in the activity by {@link
* #setMediaController(Activity, MediaControllerCompat)} for sending media key and volume events.
*
* <p>This is compatible with {@link Activity#getMediaController()}.
*
* @param activity The activity to get the media controller from, must not be null.
* @return The controller which should receive events.
* @see #setMediaController(Activity, MediaControllerCompat)
*/
@Nullable
public static MediaControllerCompat getMediaController(Activity activity) {
Object tag = activity.getWindow().getDecorView().getTag(R.id.media_controller_compat_view_tag);
if (tag instanceof MediaControllerCompat) {
return (MediaControllerCompat) tag;
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
return MediaControllerImplApi21.getMediaController(activity);
}
return null;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */ @SuppressWarnings("WeakerAccess") /* synthetic access */
static void validateCustomAction(@Nullable String action, @Nullable Bundle args) { static void validateCustomAction(@Nullable String action, @Nullable Bundle args) {
if (action == null) { if (action == null) {
@ -213,18 +166,13 @@ public final class MediaControllerCompat {
* @param sessionToken The token of the session to be controlled. * @param sessionToken The token of the session to be controlled.
*/ */
public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) { public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) {
if (sessionToken == null) {
throw new IllegalArgumentException("sessionToken must not be null");
}
mRegisteredCallbacks = Collections.synchronizedSet(new HashSet<>()); mRegisteredCallbacks = Collections.synchronizedSet(new HashSet<>());
mToken = sessionToken; mToken = sessionToken;
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
mImpl = new MediaControllerImplApi29(context, sessionToken); mImpl = new MediaControllerImplApi29(context, sessionToken);
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, sessionToken);
} else { } else {
mImpl = new MediaControllerImplBase(sessionToken); mImpl = new MediaControllerImplApi21(context, sessionToken);
} }
} }
@ -476,17 +424,6 @@ public final class MediaControllerCompat {
return mToken; return mToken;
} }
/**
* Gets the SessionToken in media2 as VersionedParcelable for the session that this controller is
* connected to.
*
* @return The session's token as VersionedParcelable.
*/
@Nullable
public VersionedParcelable getSession2Token() {
return mToken.getSession2Token();
}
/** /**
* Sets the volume of the output this session is playing on. The command will be ignored if it * Sets the volume of the output this session is playing on. The command will be ignored if it
* does not support {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in {@link * does not support {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in {@link
@ -516,16 +453,6 @@ public final class MediaControllerCompat {
mImpl.adjustVolume(direction, flags); mImpl.adjustVolume(direction, flags);
} }
/**
* Adds a callback to receive updates from the Session. Updates will be posted on the caller's
* thread.
*
* @param callback The callback object, must not be null.
*/
public void registerCallback(Callback callback) {
registerCallback(callback, null);
}
/** /**
* Adds a callback to receive updates from the session. Updates will be posted on the specified * Adds a callback to receive updates from the session. Updates will be posted on the specified
* handler's thread. * handler's thread.
@ -533,11 +460,7 @@ public final class MediaControllerCompat {
* @param callback The callback object, must not be null. * @param callback The callback object, must not be null.
* @param handler The handler to post updates on. If null the callers thread will be used. * @param handler The handler to post updates on. If null the callers thread will be used.
*/ */
@SuppressWarnings("deprecation")
public void registerCallback(Callback callback, @Nullable Handler handler) { public void registerCallback(Callback callback, @Nullable Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
if (!mRegisteredCallbacks.add(callback)) { if (!mRegisteredCallbacks.add(callback)) {
Log.w(TAG, "the callback has already been registered"); Log.w(TAG, "the callback has already been registered");
return; return;
@ -556,9 +479,6 @@ public final class MediaControllerCompat {
* @param callback The callback to remove * @param callback The callback to remove
*/ */
public void unregisterCallback(Callback callback) { public void unregisterCallback(Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
if (!mRegisteredCallbacks.remove(callback)) { if (!mRegisteredCallbacks.remove(callback)) {
Log.w(TAG, "the callback has never been registered"); Log.w(TAG, "the callback has never been registered");
return; return;
@ -656,12 +576,7 @@ public final class MediaControllerCompat {
// Sharing this in constructor // Sharing this in constructor
@SuppressWarnings({"assignment.type.incompatible", "argument.type.incompatible"}) @SuppressWarnings({"assignment.type.incompatible", "argument.type.incompatible"})
public Callback() { public Callback() {
if (android.os.Build.VERSION.SDK_INT >= 21) { mCallbackFwk = new MediaControllerCallback(this);
mCallbackFwk = new MediaControllerCallbackApi21(this);
} else {
mCallbackFwk = null;
mIControllerCallback = new StubCompat(this);
}
} }
/** /**
@ -789,11 +704,10 @@ public final class MediaControllerCompat {
} }
// Callback methods in this class are run on handler which was given to registerCallback(). // Callback methods in this class are run on handler which was given to registerCallback().
@RequiresApi(21) private static class MediaControllerCallback extends MediaController.Callback {
private static class MediaControllerCallbackApi21 extends MediaController.Callback {
private final WeakReference<MediaControllerCompat.Callback> mCallback; private final WeakReference<MediaControllerCompat.Callback> mCallback;
MediaControllerCallbackApi21(MediaControllerCompat.Callback callback) { MediaControllerCallback(MediaControllerCompat.Callback callback) {
mCallback = new WeakReference<>(callback); mCallback = new WeakReference<>(callback);
} }
@ -870,7 +784,7 @@ public final class MediaControllerCompat {
callback.onAudioInfoChanged( callback.onAudioInfoChanged(
new PlaybackInfo( new PlaybackInfo(
info.getPlaybackType(), info.getPlaybackType(),
checkNotNull(AudioAttributesCompat.wrap(info.getAudioAttributes())), AudioAttributesCompat.wrap(info.getAudioAttributes()),
info.getVolumeControl(), info.getVolumeControl(),
info.getMaxVolume(), info.getMaxVolume(),
info.getCurrentVolume())); info.getCurrentVolume()));
@ -886,7 +800,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onEvent(@Nullable String event, @Nullable Bundle extras) throws RemoteException { public void onEvent(@Nullable String event, @Nullable Bundle extras) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_EVENT, event, extras); callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
@ -894,7 +808,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onSessionDestroyed() throws RemoteException { public void onSessionDestroyed() {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null); callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
@ -902,8 +816,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state) public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state) {
throws RemoteException {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null); callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
@ -911,7 +824,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) throws RemoteException { public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null); callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
@ -919,7 +832,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onQueueChanged(@Nullable List<QueueItem> queue) throws RemoteException { public void onQueueChanged(@Nullable List<QueueItem> queue) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null); callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
@ -927,7 +840,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onQueueTitleChanged(@Nullable CharSequence title) throws RemoteException { public void onQueueTitleChanged(@Nullable CharSequence title) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null); callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
@ -935,7 +848,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException { public void onCaptioningEnabledChanged(boolean enabled) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null); callback.postToHandler(MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
@ -943,7 +856,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onRepeatModeChanged(int repeatMode) throws RemoteException { public void onRepeatModeChanged(int repeatMode) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null); callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
@ -951,12 +864,12 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException { public void onShuffleModeChangedRemoved(boolean enabled) {
// Do nothing. // Do nothing.
} }
@Override @Override
public void onShuffleModeChanged(int shuffleMode) throws RemoteException { public void onShuffleModeChanged(int shuffleMode) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null); callback.postToHandler(MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
@ -964,7 +877,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onExtrasChanged(@Nullable Bundle extras) throws RemoteException { public void onExtrasChanged(@Nullable Bundle extras) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null); callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
@ -972,7 +885,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) throws RemoteException { public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
PlaybackInfo pi = null; PlaybackInfo pi = null;
@ -990,7 +903,7 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onSessionReady() throws RemoteException { public void onSessionReady() {
MediaControllerCompat.Callback callback = mCallback.get(); MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) { if (callback != null) {
callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null); callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
@ -1231,13 +1144,6 @@ public final class MediaControllerCompat {
*/ */
public void setPlaybackSpeed(float speed) {} public void setPlaybackSpeed(float speed) {}
/**
* Enables/disables captioning for this session.
*
* @param enabled {@code true} to enable captioning, {@code false} to disable.
*/
public abstract void setCaptioningEnabled(boolean enabled);
/** /**
* Sets the repeat mode for this session. * Sets the repeat mode for this session.
* *
@ -1448,533 +1354,6 @@ public final class MediaControllerCompat {
Object getMediaController(); Object getMediaController();
} }
static class MediaControllerImplBase implements MediaControllerImpl {
private IMediaSession mBinder;
@Nullable private TransportControls mTransportControls;
@Nullable private Bundle mSessionInfo;
MediaControllerImplBase(MediaSessionCompat.Token token) {
mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
}
@Override
public void registerCallback(Callback callback, Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("callback may not be null.");
}
try {
mBinder.asBinder().linkToDeath(callback, 0);
mBinder.registerCallbackListener(checkNotNull(callback.mIControllerCallback));
callback.postToHandler(Callback.MessageHandler.MSG_SESSION_READY, null, null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerCallback.", e);
callback.postToHandler(Callback.MessageHandler.MSG_DESTROYED, null, null);
}
}
@Override
public void unregisterCallback(Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback may not be null.");
}
try {
mBinder.unregisterCallbackListener(checkNotNull(callback.mIControllerCallback));
mBinder.asBinder().unlinkToDeath(callback, 0);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterCallback.", e);
}
}
@Override
public boolean dispatchMediaButtonEvent(KeyEvent event) {
if (event == null) {
throw new IllegalArgumentException("event may not be null.");
}
try {
mBinder.sendMediaButton(event);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e);
}
return false;
}
@Override
public TransportControls getTransportControls() {
if (mTransportControls == null) {
mTransportControls = new TransportControlsBase(mBinder);
}
return mTransportControls;
}
@Nullable
@Override
public PlaybackStateCompat getPlaybackState() {
try {
return mBinder.getPlaybackState();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getPlaybackState.", e);
}
return null;
}
@Nullable
@Override
public MediaMetadataCompat getMetadata() {
try {
return mBinder.getMetadata();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getMetadata.", e);
}
return null;
}
@Nullable
@Override
public List<QueueItem> getQueue() {
try {
return mBinder.getQueue();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getQueue.", e);
}
return null;
}
@Override
public void addQueueItem(MediaDescriptionCompat description) {
try {
long flags = mBinder.getFlags();
if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
throw new UnsupportedOperationException(
"This session doesn't support queue management operations");
}
mBinder.addQueueItem(description);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in addQueueItem.", e);
}
}
@Override
public void addQueueItem(MediaDescriptionCompat description, int index) {
try {
long flags = mBinder.getFlags();
if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
throw new UnsupportedOperationException(
"This session doesn't support queue management operations");
}
mBinder.addQueueItemAt(description, index);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in addQueueItemAt.", e);
}
}
@Override
public void removeQueueItem(MediaDescriptionCompat description) {
try {
long flags = mBinder.getFlags();
if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
throw new UnsupportedOperationException(
"This session doesn't support queue management operations");
}
mBinder.removeQueueItem(description);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in removeQueueItem.", e);
}
}
@Nullable
@Override
public CharSequence getQueueTitle() {
try {
return mBinder.getQueueTitle();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getQueueTitle.", e);
}
return null;
}
@Nullable
@Override
public Bundle getExtras() {
try {
return mBinder.getExtras();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getExtras.", e);
}
return null;
}
@Override
public int getRatingType() {
try {
return mBinder.getRatingType();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getRatingType.", e);
}
return 0;
}
@Override
public boolean isCaptioningEnabled() {
try {
return mBinder.isCaptioningEnabled();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
}
return false;
}
@Override
public int getRepeatMode() {
try {
return mBinder.getRepeatMode();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getRepeatMode.", e);
}
return PlaybackStateCompat.REPEAT_MODE_INVALID;
}
@Override
public int getShuffleMode() {
try {
return mBinder.getShuffleMode();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getShuffleMode.", e);
}
return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
}
@Override
public long getFlags() {
try {
return mBinder.getFlags();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getFlags.", e);
}
return 0;
}
@Nullable
@Override
public PlaybackInfo getPlaybackInfo() {
try {
ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
if (info == null) {
return null;
}
PlaybackInfo pi =
new PlaybackInfo(
info.volumeType,
info.audioStream,
info.controlType,
info.maxVolume,
info.currentVolume);
return pi;
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getPlaybackInfo.", e);
}
return null;
}
@Nullable
@Override
public PendingIntent getSessionActivity() {
try {
return mBinder.getLaunchPendingIntent();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getSessionActivity.", e);
}
return null;
}
@Override
public void setVolumeTo(int value, int flags) {
try {
mBinder.setVolumeTo(value, flags, null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setVolumeTo.", e);
}
}
@Override
public void adjustVolume(int direction, int flags) {
try {
mBinder.adjustVolume(direction, flags, null);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustVolume.", e);
}
}
@Override
public void sendCommand(String command, @Nullable Bundle params, @Nullable ResultReceiver cb) {
try {
mBinder.sendCommand(
command, params, cb == null ? null : new MediaSessionCompat.ResultReceiverWrapper(cb));
} catch (RemoteException e) {
Log.e(TAG, "Dead object in sendCommand.", e);
}
}
@Override
public boolean isSessionReady() {
return true;
}
@Nullable
@Override
public String getPackageName() {
try {
return mBinder.getPackageName();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getPackageName.", e);
}
return null;
}
@Override
public Bundle getSessionInfo() {
try {
mSessionInfo = mBinder.getSessionInfo();
} catch (RemoteException e) {
Log.d(TAG, "Dead object in getSessionInfo.", e);
}
mSessionInfo = MediaSessionCompat.unparcelWithClassLoader(mSessionInfo);
return mSessionInfo == null ? Bundle.EMPTY : new Bundle(mSessionInfo);
}
@Nullable
@Override
public Object getMediaController() {
return null;
}
}
static class TransportControlsBase extends TransportControls {
private IMediaSession mBinder;
public TransportControlsBase(IMediaSession binder) {
mBinder = binder;
}
@Override
public void prepare() {
try {
mBinder.prepare();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in prepare.", e);
}
}
@Override
public void prepareFromMediaId(String mediaId, @Nullable Bundle extras) {
try {
mBinder.prepareFromMediaId(mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in prepareFromMediaId.", e);
}
}
@Override
public void prepareFromSearch(String query, @Nullable Bundle extras) {
try {
mBinder.prepareFromSearch(query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in prepareFromSearch.", e);
}
}
@Override
public void prepareFromUri(Uri uri, @Nullable Bundle extras) {
try {
mBinder.prepareFromUri(uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in prepareFromUri.", e);
}
}
@Override
public void play() {
try {
mBinder.play();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in play.", e);
}
}
@Override
public void playFromMediaId(String mediaId, @Nullable Bundle extras) {
try {
mBinder.playFromMediaId(mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in playFromMediaId.", e);
}
}
@Override
public void playFromSearch(String query, @Nullable Bundle extras) {
try {
mBinder.playFromSearch(query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in playFromSearch.", e);
}
}
@Override
public void playFromUri(Uri uri, @Nullable Bundle extras) {
try {
mBinder.playFromUri(uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in playFromUri.", e);
}
}
@Override
public void skipToQueueItem(long id) {
try {
mBinder.skipToQueueItem(id);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in skipToQueueItem.", e);
}
}
@Override
public void pause() {
try {
mBinder.pause();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in pause.", e);
}
}
@Override
public void stop() {
try {
mBinder.stop();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in stop.", e);
}
}
@Override
public void seekTo(long pos) {
try {
mBinder.seekTo(pos);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in seekTo.", e);
}
}
@Override
public void fastForward() {
try {
mBinder.fastForward();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in fastForward.", e);
}
}
@Override
public void skipToNext() {
try {
mBinder.next();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in skipToNext.", e);
}
}
@Override
public void rewind() {
try {
mBinder.rewind();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in rewind.", e);
}
}
@Override
public void skipToPrevious() {
try {
mBinder.previous();
} catch (RemoteException e) {
Log.e(TAG, "Dead object in skipToPrevious.", e);
}
}
@Override
public void setRating(RatingCompat rating) {
try {
mBinder.rate(rating);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setRating.", e);
}
}
@Override
public void setRating(RatingCompat rating, @Nullable Bundle extras) {
try {
mBinder.rateWithExtras(rating, extras);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setRating.", e);
}
}
@Override
public void setPlaybackSpeed(float speed) {
if (speed == 0.0f) {
throw new IllegalArgumentException("speed must not be zero");
}
try {
mBinder.setPlaybackSpeed(speed);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setPlaybackSpeed.", e);
}
}
@Override
public void setCaptioningEnabled(boolean enabled) {
try {
mBinder.setCaptioningEnabled(enabled);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setCaptioningEnabled.", e);
}
}
@Override
public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
try {
mBinder.setRepeatMode(repeatMode);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setRepeatMode.", e);
}
}
@Override
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
try {
mBinder.setShuffleMode(shuffleMode);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setShuffleMode.", e);
}
}
@Override
public void sendCustomAction(CustomAction customAction, @Nullable Bundle args) {
sendCustomAction(customAction.getAction(), args);
}
@Override
public void sendCustomAction(String action, @Nullable Bundle args) {
validateCustomAction(action, args);
try {
mBinder.sendCustomAction(action, args);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in sendCustomAction.", e);
}
}
}
@RequiresApi(21)
static class MediaControllerImplApi21 implements MediaControllerImpl { static class MediaControllerImplApi21 implements MediaControllerImpl {
protected final MediaController mControllerFwk; protected final MediaController mControllerFwk;
@ -1983,7 +1362,7 @@ public final class MediaControllerCompat {
@GuardedBy("mLock") @GuardedBy("mLock")
private final List<Callback> mPendingCallbacks = new ArrayList<>(); private final List<Callback> mPendingCallbacks = new ArrayList<>();
private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>(); private final HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>();
@Nullable protected Bundle mSessionInfo; @Nullable protected Bundle mSessionInfo;
@ -1993,8 +1372,7 @@ public final class MediaControllerCompat {
@SuppressWarnings({"assignment.type.incompatible", "method.invocation.invalid"}) @SuppressWarnings({"assignment.type.incompatible", "method.invocation.invalid"})
MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) { MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) {
mSessionToken = sessionToken; mSessionToken = sessionToken;
mControllerFwk = mControllerFwk = new MediaController(context, mSessionToken.getToken());
new MediaController(context, (MediaSession.Token) checkNotNull(mSessionToken.getToken()));
if (mSessionToken.getExtraBinder() == null) { if (mSessionToken.getExtraBinder() == null) {
requestExtraBinder(); requestExtraBinder();
} }
@ -2215,7 +1593,7 @@ public final class MediaControllerCompat {
return volumeInfoFwk != null return volumeInfoFwk != null
? new PlaybackInfo( ? new PlaybackInfo(
volumeInfoFwk.getPlaybackType(), volumeInfoFwk.getPlaybackType(),
checkNotNull(AudioAttributesCompat.wrap(volumeInfoFwk.getAudioAttributes())), AudioAttributesCompat.wrap(volumeInfoFwk.getAudioAttributes()),
volumeInfoFwk.getVolumeControl(), volumeInfoFwk.getVolumeControl(),
volumeInfoFwk.getMaxVolume(), volumeInfoFwk.getMaxVolume(),
volumeInfoFwk.getCurrentVolume()) volumeInfoFwk.getCurrentVolume())
@ -2304,29 +1682,8 @@ public final class MediaControllerCompat {
mPendingCallbacks.clear(); mPendingCallbacks.clear();
} }
@SuppressWarnings("argument.type.incompatible") // Activity.setMediaController is not annotated
static void setMediaController(Activity activity, MediaControllerCompat mediaControllerCompat) {
MediaController controllerFwk = null;
if (mediaControllerCompat != null) {
Object sessionTokenObj = mediaControllerCompat.getSessionToken().getToken();
controllerFwk = new MediaController(activity, (MediaSession.Token) sessionTokenObj);
}
activity.setMediaController(controllerFwk);
}
@Nullable
static MediaControllerCompat getMediaController(Activity activity) {
MediaController controllerFwk = activity.getMediaController();
if (controllerFwk == null) {
return null;
}
MediaSession.Token sessionTokenFwk = controllerFwk.getSessionToken();
return new MediaControllerCompat(
activity, MediaSessionCompat.Token.fromToken(sessionTokenFwk));
}
private static class ExtraBinderRequestResultReceiver extends ResultReceiver { private static class ExtraBinderRequestResultReceiver extends ResultReceiver {
private WeakReference<MediaControllerImplApi21> mMediaControllerImpl; private final WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl) { ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl) {
super(null /* handler */); super(null /* handler */);
@ -2357,37 +1714,37 @@ public final class MediaControllerCompat {
} }
@Override @Override
public void onSessionDestroyed() throws RemoteException { public void onSessionDestroyed() {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) throws RemoteException { public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void onQueueChanged(@Nullable List<QueueItem> queue) throws RemoteException { public void onQueueChanged(@Nullable List<QueueItem> queue) {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void onQueueTitleChanged(@Nullable CharSequence title) throws RemoteException { public void onQueueTitleChanged(@Nullable CharSequence title) {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void onExtrasChanged(@Nullable Bundle extras) throws RemoteException { public void onExtrasChanged(@Nullable Bundle extras) {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) throws RemoteException { public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) {
// Will not be called. // Will not be called.
throw new AssertionError(); throw new AssertionError();
} }
@ -2411,7 +1768,6 @@ public final class MediaControllerCompat {
} }
} }
@RequiresApi(21)
static class TransportControlsApi21 extends TransportControls { static class TransportControlsApi21 extends TransportControls {
protected final MediaController.TransportControls mControlsFwk; protected final MediaController.TransportControls mControlsFwk;
@ -2491,7 +1847,7 @@ public final class MediaControllerCompat {
@SuppressWarnings("argument.type.incompatible") // Platform controller accepts null rating @SuppressWarnings("argument.type.incompatible") // Platform controller accepts null rating
@Override @Override
public void setRating(RatingCompat rating) { public void setRating(RatingCompat rating) {
mControlsFwk.setRating(rating != null ? (Rating) rating.getRating() : null); mControlsFwk.setRating((Rating) rating.getRating());
} }
@Override @Override
@ -2514,13 +1870,6 @@ public final class MediaControllerCompat {
sendCustomAction(MediaSessionCompat.ACTION_SET_PLAYBACK_SPEED, bundle); sendCustomAction(MediaSessionCompat.ACTION_SET_PLAYBACK_SPEED, bundle);
} }
@Override
public void setCaptioningEnabled(boolean enabled) {
Bundle bundle = new Bundle();
bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
sendCustomAction(MediaSessionCompat.ACTION_SET_CAPTIONING_ENABLED, bundle);
}
@Override @Override
public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
@ -2549,7 +1898,7 @@ public final class MediaControllerCompat {
@Override @Override
public void playFromUri(Uri uri, @Nullable Bundle extras) { public void playFromUri(Uri uri, @Nullable Bundle extras) {
if (uri == null || Uri.EMPTY.equals(uri)) { if (Uri.EMPTY.equals(uri)) {
throw new IllegalArgumentException("You must specify a non-empty Uri for playFromUri."); throw new IllegalArgumentException("You must specify a non-empty Uri for playFromUri.");
} }
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();

View File

@ -16,7 +16,6 @@
package androidx.media3.session.legacy; package androidx.media3.session.legacy;
import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -26,7 +25,6 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
@ -34,13 +32,12 @@ import androidx.media3.common.util.UnstableApi;
/** /**
* A simple set of metadata for a media item suitable for display. This can be created using the * A simple set of metadata for a media item suitable for display. This can be created using the
* Builder or retrieved from existing metadata using {@link MediaMetadataCompat#getDescription()}. * Builder.
*/ */
@UnstableApi @UnstableApi
@RestrictTo(LIBRARY) @RestrictTo(LIBRARY)
@SuppressLint("BanParcelableUsage") @SuppressLint("BanParcelableUsage")
public final class MediaDescriptionCompat implements Parcelable { public final class MediaDescriptionCompat implements Parcelable {
private static final String TAG = "MediaDescriptionCompat";
/** /**
* Used as a long extra field to indicate the bluetooth folder type of the media item as specified * Used as a long extra field to indicate the bluetooth folder type of the media item as specified
@ -60,7 +57,8 @@ public final class MediaDescriptionCompat implements Parcelable {
* *
* @see #getExtras() * @see #getExtras()
*/ */
public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE"; @SuppressLint("InlinedApi") // Inlined compile time constant
public static final String EXTRA_BT_FOLDER_TYPE = MediaDescription.EXTRA_BT_FOLDER_TYPE;
/** /**
* The type of folder that is unknown or contains media elements of mixed types as specified in * The type of folder that is unknown or contains media elements of mixed types as specified in
@ -196,20 +194,6 @@ public final class MediaDescriptionCompat implements Parcelable {
mMediaUri = mediaUri; mMediaUri = mediaUri;
} }
@SuppressWarnings("deprecation")
MediaDescriptionCompat(Parcel in) {
mMediaId = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
ClassLoader loader = getClass().getClassLoader();
mIcon = in.readParcelable(loader);
mIconUri = in.readParcelable(loader);
mExtras = in.readBundle(loader);
mMediaUri = in.readParcelable(loader);
}
/** Returns the media id or null. See {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}. */ /** Returns the media id or null. See {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}. */
@Nullable @Nullable
public String getMediaId() { public String getMediaId() {
@ -293,18 +277,7 @@ public final class MediaDescriptionCompat implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
if (Build.VERSION.SDK_INT < 21) { getMediaDescription().writeToParcel(dest, flags);
dest.writeString(mMediaId);
TextUtils.writeToParcel(mTitle, dest, flags);
TextUtils.writeToParcel(mSubtitle, dest, flags);
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeParcelable(mIcon, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeBundle(mExtras);
dest.writeParcelable(mMediaUri, flags);
} else {
((MediaDescription) getMediaDescription()).writeToParcel(dest, flags);
}
} }
@Override @Override
@ -315,22 +288,19 @@ public final class MediaDescriptionCompat implements Parcelable {
/** /**
* Gets the underlying framework {@link android.media.MediaDescription} object. * Gets the underlying framework {@link android.media.MediaDescription} object.
* *
* <p>This method is only supported on {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. * @return An equivalent {@link android.media.MediaDescription} object.
*
* @return An equivalent {@link android.media.MediaDescription} object, or null if none.
*/ */
@RequiresApi(21) public MediaDescription getMediaDescription() {
public Object getMediaDescription() {
if (mDescriptionFwk != null) { if (mDescriptionFwk != null) {
return mDescriptionFwk; return mDescriptionFwk;
} }
MediaDescription.Builder bob = Api21Impl.createBuilder(); MediaDescription.Builder bob = new MediaDescription.Builder();
Api21Impl.setMediaId(bob, mMediaId); bob.setMediaId(mMediaId);
Api21Impl.setTitle(bob, mTitle); bob.setTitle(mTitle);
Api21Impl.setSubtitle(bob, mSubtitle); bob.setSubtitle(mSubtitle);
Api21Impl.setDescription(bob, mDescription); bob.setDescription(mDescription);
Api21Impl.setIconBitmap(bob, mIcon); bob.setIconBitmap(mIcon);
Api21Impl.setIconUri(bob, mIconUri); bob.setIconUri(mIconUri);
// Media URI was not added until API 23, so add it to the Bundle of extras to // Media URI was not added until API 23, so add it to the Bundle of extras to
// ensure the data is not lost - this ensures that // ensure the data is not lost - this ensures that
// fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns
@ -344,14 +314,14 @@ public final class MediaDescriptionCompat implements Parcelable {
extras = new Bundle(mExtras); extras = new Bundle(mExtras);
} }
extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri); extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
Api21Impl.setExtras(bob, extras); bob.setExtras(extras);
} else { } else {
Api21Impl.setExtras(bob, mExtras); bob.setExtras(mExtras);
} }
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
Api23Impl.setMediaUri(bob, mMediaUri); Api23Impl.setMediaUri(bob, mMediaUri);
} }
mDescriptionFwk = Api21Impl.build(bob); mDescriptionFwk = bob.build();
return mDescriptionFwk; return mDescriptionFwk;
} }
@ -359,24 +329,18 @@ public final class MediaDescriptionCompat implements Parcelable {
/** /**
* Creates an instance from a framework {@link android.media.MediaDescription} object. * Creates an instance from a framework {@link android.media.MediaDescription} object.
* *
* <p>This method is only supported on API 21+. * @param description A {@link android.media.MediaDescription} object.
* * @return An equivalent {@link MediaMetadataCompat} object.
* @param descriptionObj A {@link android.media.MediaDescription} object, or null if none.
* @return An equivalent {@link MediaMetadataCompat} object, or null if none.
*/ */
@Nullable public static MediaDescriptionCompat fromMediaDescription(MediaDescription description) {
@SuppressWarnings("deprecation")
public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
if (descriptionObj != null && Build.VERSION.SDK_INT >= 21) {
Builder bob = new Builder(); Builder bob = new Builder();
MediaDescription description = (MediaDescription) descriptionObj; bob.setMediaId(description.getMediaId());
bob.setMediaId(Api21Impl.getMediaId(description)); bob.setTitle(description.getTitle());
bob.setTitle(Api21Impl.getTitle(description)); bob.setSubtitle(description.getSubtitle());
bob.setSubtitle(Api21Impl.getSubtitle(description)); bob.setDescription(description.getDescription());
bob.setDescription(Api21Impl.getDescription(description)); bob.setIconBitmap(description.getIconBitmap());
bob.setIconBitmap(Api21Impl.getIconBitmap(description)); bob.setIconUri(description.getIconUri());
bob.setIconUri(Api21Impl.getIconUri(description)); Bundle extras = description.getExtras();
Bundle extras = Api21Impl.getExtras(description);
extras = MediaSessionCompat.unparcelWithClassLoader(extras); extras = MediaSessionCompat.unparcelWithClassLoader(extras);
if (extras != null) { if (extras != null) {
extras = new Bundle(extras); extras = new Bundle(extras);
@ -407,23 +371,14 @@ public final class MediaDescriptionCompat implements Parcelable {
} }
MediaDescriptionCompat descriptionCompat = bob.build(); MediaDescriptionCompat descriptionCompat = bob.build();
descriptionCompat.mDescriptionFwk = description; descriptionCompat.mDescriptionFwk = description;
return descriptionCompat; return descriptionCompat;
} else {
return null;
}
} }
public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR = public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR =
new Parcelable.Creator<MediaDescriptionCompat>() { new Parcelable.Creator<MediaDescriptionCompat>() {
@Override @Override
public MediaDescriptionCompat createFromParcel(Parcel in) { public MediaDescriptionCompat createFromParcel(Parcel in) {
if (Build.VERSION.SDK_INT < 21) { return fromMediaDescription(MediaDescription.CREATOR.createFromParcel(in));
return new MediaDescriptionCompat(in);
} else {
return checkNotNull(
fromMediaDescription(MediaDescription.CREATOR.createFromParcel(in)));
}
} }
@Override @Override
@ -545,83 +500,6 @@ public final class MediaDescriptionCompat implements Parcelable {
} }
} }
@RequiresApi(21)
private static class Api21Impl {
private Api21Impl() {}
static MediaDescription.Builder createBuilder() {
return new MediaDescription.Builder();
}
static void setMediaId(MediaDescription.Builder builder, @Nullable String mediaId) {
builder.setMediaId(mediaId);
}
static void setTitle(MediaDescription.Builder builder, @Nullable CharSequence title) {
builder.setTitle(title);
}
static void setSubtitle(MediaDescription.Builder builder, @Nullable CharSequence subtitle) {
builder.setSubtitle(subtitle);
}
static void setDescription(
MediaDescription.Builder builder, @Nullable CharSequence description) {
builder.setDescription(description);
}
static void setIconBitmap(MediaDescription.Builder builder, @Nullable Bitmap icon) {
builder.setIconBitmap(icon);
}
static void setIconUri(MediaDescription.Builder builder, @Nullable Uri iconUri) {
builder.setIconUri(iconUri);
}
static void setExtras(MediaDescription.Builder builder, @Nullable Bundle extras) {
builder.setExtras(extras);
}
static MediaDescription build(MediaDescription.Builder builder) {
return builder.build();
}
@Nullable
static String getMediaId(MediaDescription description) {
return description.getMediaId();
}
@Nullable
static CharSequence getTitle(MediaDescription description) {
return description.getTitle();
}
@Nullable
static CharSequence getSubtitle(MediaDescription description) {
return description.getSubtitle();
}
@Nullable
static CharSequence getDescription(MediaDescription description) {
return description.getDescription();
}
@Nullable
static Bitmap getIconBitmap(MediaDescription description) {
return description.getIconBitmap();
}
@Nullable
static Uri getIconUri(MediaDescription description) {
return description.getIconUri();
}
@Nullable
static Bundle getExtras(MediaDescription description) {
return description.getExtras();
}
}
@RequiresApi(23) @RequiresApi(23)
private static class Api23Impl { private static class Api23Impl {
private Api23Impl() {} private Api23Impl() {}

View File

@ -22,23 +22,18 @@ import android.annotation.SuppressLint;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.MediaMetadata; import android.media.MediaMetadata;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef; import androidx.annotation.StringDef;
import androidx.collection.ArrayMap; import androidx.collection.ArrayMap;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaControllerCompat.TransportControls; import androidx.media3.session.legacy.MediaControllerCompat.TransportControls;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Set;
/** Contains metadata about an item, such as the title, artist, etc. */ /** Contains metadata about an item, such as the title, artist, etc. */
@UnstableApi @UnstableApi
@ -48,55 +43,55 @@ public final class MediaMetadataCompat implements Parcelable {
private static final String TAG = "MediaMetadata"; private static final String TAG = "MediaMetadata";
/** The title of the media. */ /** The title of the media. */
public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE"; public static final String METADATA_KEY_TITLE = MediaMetadata.METADATA_KEY_TITLE;
/** The artist of the media. */ /** The artist of the media. */
public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST"; public static final String METADATA_KEY_ARTIST = MediaMetadata.METADATA_KEY_ARTIST;
/** /**
* The duration of the media in ms. A negative duration indicates that the duration is unknown (or * The duration of the media in ms. A negative duration indicates that the duration is unknown (or
* infinite). * infinite).
*/ */
public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION"; public static final String METADATA_KEY_DURATION = MediaMetadata.METADATA_KEY_DURATION;
/** The album title for the media. */ /** The album title for the media. */
public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM"; public static final String METADATA_KEY_ALBUM = MediaMetadata.METADATA_KEY_ALBUM;
/** The author of the media. */ /** The author of the media. */
public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR"; public static final String METADATA_KEY_AUTHOR = MediaMetadata.METADATA_KEY_AUTHOR;
/** The writer of the media. */ /** The writer of the media. */
public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER"; public static final String METADATA_KEY_WRITER = MediaMetadata.METADATA_KEY_WRITER;
/** The composer of the media. */ /** The composer of the media. */
public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER"; public static final String METADATA_KEY_COMPOSER = MediaMetadata.METADATA_KEY_COMPOSER;
/** The compilation status of the media. */ /** The compilation status of the media. */
public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION"; public static final String METADATA_KEY_COMPILATION = MediaMetadata.METADATA_KEY_COMPILATION;
/** /**
* The date the media was created or published. The format is unspecified but RFC 3339 is * The date the media was created or published. The format is unspecified but RFC 3339 is
* recommended. * recommended.
*/ */
public static final String METADATA_KEY_DATE = "android.media.metadata.DATE"; public static final String METADATA_KEY_DATE = MediaMetadata.METADATA_KEY_DATE;
/** The year the media was created or published as a long. */ /** The year the media was created or published as a long. */
public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR"; public static final String METADATA_KEY_YEAR = MediaMetadata.METADATA_KEY_YEAR;
/** The genre of the media. */ /** The genre of the media. */
public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE"; public static final String METADATA_KEY_GENRE = MediaMetadata.METADATA_KEY_GENRE;
/** The track number for the media. */ /** The track number for the media. */
public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER"; public static final String METADATA_KEY_TRACK_NUMBER = MediaMetadata.METADATA_KEY_TRACK_NUMBER;
/** The number of tracks in the media's original source. */ /** The number of tracks in the media's original source. */
public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS"; public static final String METADATA_KEY_NUM_TRACKS = MediaMetadata.METADATA_KEY_NUM_TRACKS;
/** The disc number for the media's original source. */ /** The disc number for the media's original source. */
public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER"; public static final String METADATA_KEY_DISC_NUMBER = MediaMetadata.METADATA_KEY_DISC_NUMBER;
/** The artist for the album of the media's original source. */ /** The artist for the album of the media's original source. */
public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST"; public static final String METADATA_KEY_ALBUM_ARTIST = MediaMetadata.METADATA_KEY_ALBUM_ARTIST;
/** /**
* The artwork for the media as a {@link Bitmap}. * The artwork for the media as a {@link Bitmap}.
@ -104,55 +99,55 @@ public final class MediaMetadataCompat implements Parcelable {
* <p>The artwork should be relatively small and may be scaled down if it is too large. For higher * <p>The artwork should be relatively small and may be scaled down if it is too large. For higher
* resolution artwork {@link #METADATA_KEY_ART_URI} should be used instead. * resolution artwork {@link #METADATA_KEY_ART_URI} should be used instead.
*/ */
public static final String METADATA_KEY_ART = "android.media.metadata.ART"; public static final String METADATA_KEY_ART = MediaMetadata.METADATA_KEY_ART;
/** The artwork for the media as a Uri style String. */ /** The artwork for the media as a Uri style String. */
public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI"; public static final String METADATA_KEY_ART_URI = MediaMetadata.METADATA_KEY_ART_URI;
/** /**
* The artwork for the album of the media's original source as a {@link Bitmap}. The artwork * The artwork for the album of the media's original source as a {@link Bitmap}. The artwork
* should be relatively small and may be scaled down if it is too large. For higher resolution * should be relatively small and may be scaled down if it is too large. For higher resolution
* artwork {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead. * artwork {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
*/ */
public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART"; public static final String METADATA_KEY_ALBUM_ART = MediaMetadata.METADATA_KEY_ALBUM_ART;
/** The artwork for the album of the media's original source as a Uri style String. */ /** The artwork for the album of the media's original source as a Uri style String. */
public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI"; public static final String METADATA_KEY_ALBUM_ART_URI = MediaMetadata.METADATA_KEY_ALBUM_ART_URI;
/** /**
* The user's rating for the media. * The user's rating for the media.
* *
* @see RatingCompat * @see RatingCompat
*/ */
public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING"; public static final String METADATA_KEY_USER_RATING = MediaMetadata.METADATA_KEY_USER_RATING;
/** /**
* The overall rating for the media. * The overall rating for the media.
* *
* @see RatingCompat * @see RatingCompat
*/ */
public static final String METADATA_KEY_RATING = "android.media.metadata.RATING"; public static final String METADATA_KEY_RATING = MediaMetadata.METADATA_KEY_RATING;
/** /**
* A title that is suitable for display to the user. This will generally be the same as {@link * A title that is suitable for display to the user. This will generally be the same as {@link
* #METADATA_KEY_TITLE} but may differ for some formats. When displaying media described by this * #METADATA_KEY_TITLE} but may differ for some formats. When displaying media described by this
* metadata this should be preferred if present. * metadata this should be preferred if present.
*/ */
public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE"; public static final String METADATA_KEY_DISPLAY_TITLE = MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
/** /**
* A subtitle that is suitable for display to the user. When displaying a second line for media * A subtitle that is suitable for display to the user. When displaying a second line for media
* described by this metadata this should be preferred to other fields if present. * described by this metadata this should be preferred to other fields if present.
*/ */
public static final String METADATA_KEY_DISPLAY_SUBTITLE = public static final String METADATA_KEY_DISPLAY_SUBTITLE =
"android.media.metadata.DISPLAY_SUBTITLE"; MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE;
/** /**
* A description that is suitable for display to the user. When displaying more information for * A description that is suitable for display to the user. When displaying more information for
* media described by this metadata this should be preferred to other fields if present. * media described by this metadata this should be preferred to other fields if present.
*/ */
public static final String METADATA_KEY_DISPLAY_DESCRIPTION = public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
"android.media.metadata.DISPLAY_DESCRIPTION"; MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION;
/** /**
* An icon or thumbnail that is suitable for display to the user. When displaying an icon for * An icon or thumbnail that is suitable for display to the user. When displaying an icon for
@ -162,7 +157,7 @@ public final class MediaMetadataCompat implements Parcelable {
* <p>The icon should be relatively small and may be scaled down if it is too large. For higher * <p>The icon should be relatively small and may be scaled down if it is too large. For higher
* resolution artwork {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead. * resolution artwork {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
*/ */
public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON"; public static final String METADATA_KEY_DISPLAY_ICON = MediaMetadata.METADATA_KEY_DISPLAY_ICON;
/** /**
* An icon or thumbnail that is suitable for display to the user. When displaying more information * An icon or thumbnail that is suitable for display to the user. When displaying more information
@ -170,20 +165,21 @@ public final class MediaMetadataCompat implements Parcelable {
* fields when present. This must be a Uri style String. * fields when present. This must be a Uri style String.
*/ */
public static final String METADATA_KEY_DISPLAY_ICON_URI = public static final String METADATA_KEY_DISPLAY_ICON_URI =
"android.media.metadata.DISPLAY_ICON_URI"; MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI;
/** /**
* A String key for identifying the content. This value is specific to the service providing the * A String key for identifying the content. This value is specific to the service providing the
* content. If used, this should be a persistent unique key for the underlying content. * content. If used, this should be a persistent unique key for the underlying content.
*/ */
public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"; public static final String METADATA_KEY_MEDIA_ID = MediaMetadata.METADATA_KEY_MEDIA_ID;
/** /**
* A Uri formatted String representing the content. This value is specific to the service * A Uri formatted String representing the content. This value is specific to the service
* providing the content. It may be used with {@link TransportControls#playFromUri(Uri, Bundle)} * providing the content. It may be used with {@link TransportControls#playFromUri(Uri, Bundle)}
* to initiate playback when provided by a {@link MediaBrowserCompat} connected to the same app. * to initiate playback when provided by a {@link MediaBrowserCompat} connected to the same app.
*/ */
public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI"; @SuppressLint("InlinedApi") // Inlined compile time constant
public static final String METADATA_KEY_MEDIA_URI = MediaMetadata.METADATA_KEY_MEDIA_URI;
/** /**
* The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth AVRCP * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth AVRCP
@ -199,7 +195,9 @@ public final class MediaMetadataCompat implements Parcelable {
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_YEARS} * <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_YEARS}
* </ul> * </ul>
*/ */
public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE"; @SuppressLint("InlinedApi") // Inlined compile time constant
public static final String METADATA_KEY_BT_FOLDER_TYPE =
MediaMetadata.METADATA_KEY_BT_FOLDER_TYPE;
/** /**
* Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A * Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A
@ -276,7 +274,7 @@ public final class MediaMetadataCompat implements Parcelable {
static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
static { static {
METADATA_KEYS_TYPE = new ArrayMap<String, Integer>(); METADATA_KEYS_TYPE = new ArrayMap<>();
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG); METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
@ -320,17 +318,8 @@ public final class MediaMetadataCompat implements Parcelable {
METADATA_KEY_COMPOSER METADATA_KEY_COMPOSER
}; };
private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
METADATA_KEY_DISPLAY_ICON, METADATA_KEY_ART, METADATA_KEY_ALBUM_ART
};
private static final @TextKey String[] PREFERRED_URI_ORDER = {
METADATA_KEY_DISPLAY_ICON_URI, METADATA_KEY_ART_URI, METADATA_KEY_ALBUM_ART_URI
};
final Bundle mBundle; final Bundle mBundle;
@Nullable private MediaMetadata mMetadataFwk; @Nullable private MediaMetadata mMetadataFwk;
@Nullable private MediaDescriptionCompat mDescription;
MediaMetadataCompat(Bundle bundle) { MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle); mBundle = new Bundle(bundle);
@ -396,7 +385,6 @@ public final class MediaMetadataCompat implements Parcelable {
* @return A {@link RatingCompat} or null * @return A {@link RatingCompat} or null
*/ */
@Nullable @Nullable
@SuppressWarnings("deprecation")
public RatingCompat getRating(@RatingKey String key) { public RatingCompat getRating(@RatingKey String key) {
RatingCompat rating = null; RatingCompat rating = null;
try { try {
@ -417,7 +405,6 @@ public final class MediaMetadataCompat implements Parcelable {
* @return A {@link Bitmap} or null * @return A {@link Bitmap} or null
*/ */
@Nullable @Nullable
@SuppressWarnings("deprecation")
public Bitmap getBitmap(@BitmapKey String key) { public Bitmap getBitmap(@BitmapKey String key) {
Bitmap bmp = null; Bitmap bmp = null;
try { try {
@ -429,93 +416,6 @@ public final class MediaMetadataCompat implements Parcelable {
return bmp; return bmp;
} }
/**
* Returns a simple description of this metadata for display purposes.
*
* @return A simple description of this metadata.
*/
public MediaDescriptionCompat getDescription() {
if (mDescription != null) {
return mDescription;
}
String mediaId = getString(METADATA_KEY_MEDIA_ID);
@NullableType CharSequence[] text = new CharSequence[3];
Bitmap icon = null;
Uri iconUri = null;
// First handle the case where display data is set already
CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
if (!TextUtils.isEmpty(displayText)) {
// If they have a display title use only display data, otherwise use
// our best bets
text[0] = displayText;
text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
} else {
// Use whatever fields we can
int textIndex = 0;
int keyIndex = 0;
while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
if (!TextUtils.isEmpty(next)) {
// Fill in the next empty bit of text
text[textIndex++] = next;
}
}
}
// Get the best art bitmap we can find
for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
if (next != null) {
icon = next;
break;
}
}
// Get the best Uri we can find
for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
String next = getString(PREFERRED_URI_ORDER[i]);
if (!TextUtils.isEmpty(next)) {
iconUri = Uri.parse(next);
break;
}
}
Uri mediaUri = null;
String mediaUriStr = getString(METADATA_KEY_MEDIA_URI);
if (!TextUtils.isEmpty(mediaUriStr)) {
mediaUri = Uri.parse(mediaUriStr);
}
MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
bob.setMediaId(mediaId);
bob.setTitle(text[0]);
bob.setSubtitle(text[1]);
bob.setDescription(text[2]);
bob.setIconBitmap(icon);
bob.setIconUri(iconUri);
bob.setMediaUri(mediaUri);
Bundle bundle = new Bundle();
if (mBundle.containsKey(METADATA_KEY_BT_FOLDER_TYPE)) {
bundle.putLong(
MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE, getLong(METADATA_KEY_BT_FOLDER_TYPE));
}
if (mBundle.containsKey(METADATA_KEY_DOWNLOAD_STATUS)) {
bundle.putLong(
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, getLong(METADATA_KEY_DOWNLOAD_STATUS));
}
if (!bundle.isEmpty()) {
bob.setExtras(bundle);
}
mDescription = bob.build();
return mDescription;
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
@ -535,15 +435,6 @@ public final class MediaMetadataCompat implements Parcelable {
return mBundle.size(); return mBundle.size();
} }
/**
* Returns a Set containing the Strings used as keys in this metadata.
*
* @return a Set of String keys
*/
public Set<String> keySet() {
return mBundle.keySet();
}
/** /**
* Gets a copy of the bundle for this metadata object. This is available to support backwards * Gets a copy of the bundle for this metadata object. This is available to support backwards
* compatibility. * compatibility.
@ -564,7 +455,7 @@ public final class MediaMetadataCompat implements Parcelable {
*/ */
@Nullable @Nullable
public static MediaMetadataCompat fromMediaMetadata(@Nullable Object metadataObj) { public static MediaMetadataCompat fromMediaMetadata(@Nullable Object metadataObj) {
if (metadataObj != null && Build.VERSION.SDK_INT >= 21) { if (metadataObj != null) {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
((MediaMetadata) metadataObj).writeToParcel(p, 0); ((MediaMetadata) metadataObj).writeToParcel(p, 0);
p.setDataPosition(0); p.setDataPosition(0);
@ -580,12 +471,9 @@ public final class MediaMetadataCompat implements Parcelable {
/** /**
* Gets the underlying framework {@link android.media.MediaMetadata} object. * Gets the underlying framework {@link android.media.MediaMetadata} object.
* *
* <p>This method is only supported on {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. * @return An equivalent {@link android.media.MediaMetadata} object.
*
* @return An equivalent {@link android.media.MediaMetadata} object, or null if none.
*/ */
@RequiresApi(21) public MediaMetadata getMediaMetadata() {
public Object getMediaMetadata() {
if (mMetadataFwk == null) { if (mMetadataFwk == null) {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
try { try {
@ -628,39 +516,6 @@ public final class MediaMetadataCompat implements Parcelable {
mBundle = new Bundle(); mBundle = new Bundle();
} }
/**
* Create a Builder using a {@link MediaMetadataCompat} instance to set the initial values. All
* fields in the source metadata will be included in the new metadata. Fields can be overwritten
* by adding the same key.
*
* @param source
*/
public Builder(MediaMetadataCompat source) {
mBundle = new Bundle(source.mBundle);
MediaSessionCompat.ensureClassLoader(mBundle);
}
/**
* Create a Builder using a {@link MediaMetadataCompat} instance to set initial values, but
* replace bitmaps with a scaled down copy if they are larger than maxBitmapSize.
*
* @param source The original metadata to copy.
* @param maxBitmapSize The maximum height/width for bitmaps contained in the metadata.
*/
@SuppressWarnings("deprecation")
public Builder(MediaMetadataCompat source, int maxBitmapSize) {
this(source);
for (String key : mBundle.keySet()) {
Object value = mBundle.get(key);
if (value instanceof Bitmap) {
Bitmap bmp = (Bitmap) value;
if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
}
}
}
}
/** /**
* Put a CharSequence value into the metadata. Custom keys may be used, but if the METADATA_KEYs * Put a CharSequence value into the metadata. Custom keys may be used, but if the METADATA_KEYs
* defined in this class are used they may only be one of the following: * defined in this class are used they may only be one of the following:
@ -819,15 +674,5 @@ public final class MediaMetadataCompat implements Parcelable {
public MediaMetadataCompat build() { public MediaMetadataCompat build() {
return new MediaMetadataCompat(mBundle); return new MediaMetadataCompat(mBundle);
} }
private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
float maxSizeF = maxSize;
float widthScale = maxSizeF / bmp.getWidth();
float heightScale = maxSizeF / bmp.getHeight();
float scale = Math.min(widthScale, heightScale);
int height = (int) (bmp.getHeight() * scale);
int width = (int) (bmp.getWidth() * scale);
return Bitmap.createScaledBitmap(bmp, width, height, true);
}
} }
} }

View File

@ -57,9 +57,6 @@ public final class MediaSessionManager {
* @return The MediaSessionManager instance for this context. * @return The MediaSessionManager instance for this context.
*/ */
public static MediaSessionManager getSessionManager(Context context) { public static MediaSessionManager getSessionManager(Context context) {
if (context == null) {
throw new IllegalArgumentException("context cannot be null");
}
synchronized (sLock) { synchronized (sLock) {
if (sSessionManager == null) { if (sSessionManager == null) {
sSessionManager = new MediaSessionManager(context.getApplicationContext()); sSessionManager = new MediaSessionManager(context.getApplicationContext());
@ -69,13 +66,7 @@ public final class MediaSessionManager {
} }
private MediaSessionManager(Context context) { private MediaSessionManager(Context context) {
if (Build.VERSION.SDK_INT >= 28) { mImpl = new MediaSessionManagerImpl(context);
mImpl = new MediaSessionManagerImplApi28(context);
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaSessionManagerImplApi21(context);
} else {
mImpl = new MediaSessionManagerImplBase(context);
}
} }
/** /**
@ -91,22 +82,9 @@ public final class MediaSessionManager {
* {@code false} otherwise. * {@code false} otherwise.
*/ */
public boolean isTrustedForMediaControl(RemoteUserInfo userInfo) { public boolean isTrustedForMediaControl(RemoteUserInfo userInfo) {
if (userInfo == null) {
throw new IllegalArgumentException("userInfo should not be null");
}
return mImpl.isTrustedForMediaControl(userInfo.mImpl); return mImpl.isTrustedForMediaControl(userInfo.mImpl);
} }
Context getContext() {
return mImpl.getContext();
}
interface MediaSessionManagerImpl {
Context getContext();
boolean isTrustedForMediaControl(RemoteUserInfoImpl userInfo);
}
interface RemoteUserInfoImpl { interface RemoteUserInfoImpl {
String getPackageName(); String getPackageName();
@ -157,10 +135,10 @@ public final class MediaSessionManager {
throw new IllegalArgumentException("packageName should be nonempty"); throw new IllegalArgumentException("packageName should be nonempty");
} }
if (Build.VERSION.SDK_INT >= 28) { if (Build.VERSION.SDK_INT >= 28) {
mImpl = new MediaSessionManagerImplApi28.RemoteUserInfoImplApi28(packageName, pid, uid); mImpl = new RemoteUserInfoImplApi28(packageName, pid, uid);
} else { } else {
// Note: We need to include IBinder to distinguish controllers in a process. // Note: We need to include IBinder to distinguish controllers in a process.
mImpl = new MediaSessionManagerImplBase.RemoteUserInfoImplBase(packageName, pid, uid); mImpl = new RemoteUserInfoImplBase(packageName, pid, uid);
} }
} }
@ -177,14 +155,13 @@ public final class MediaSessionManager {
public RemoteUserInfo(android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) { public RemoteUserInfo(android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
// Framework RemoteUserInfo doesn't ensure non-null nor non-empty package name, // Framework RemoteUserInfo doesn't ensure non-null nor non-empty package name,
// so ensure package name here instead. // so ensure package name here instead.
String packageName = String packageName = RemoteUserInfoImplApi28.getPackageName(remoteUserInfo);
MediaSessionManagerImplApi28.RemoteUserInfoImplApi28.getPackageName(remoteUserInfo);
if (packageName == null) { if (packageName == null) {
throw new NullPointerException("package shouldn't be null"); throw new NullPointerException("package shouldn't be null");
} else if (TextUtils.isEmpty(packageName)) { } else if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName should be nonempty"); throw new IllegalArgumentException("packageName should be nonempty");
} }
mImpl = new MediaSessionManagerImplApi28.RemoteUserInfoImplApi28(remoteUserInfo); mImpl = new RemoteUserInfoImplApi28(remoteUserInfo);
} }
/** /**
@ -241,8 +218,7 @@ public final class MediaSessionManager {
} }
} }
private static class MediaSessionManagerImplBase private static class MediaSessionManagerImpl {
implements MediaSessionManager.MediaSessionManagerImpl {
private static final String TAG = MediaSessionManager.TAG; private static final String TAG = MediaSessionManager.TAG;
private static final boolean DEBUG = MediaSessionManager.DEBUG; private static final boolean DEBUG = MediaSessionManager.DEBUG;
@ -255,19 +231,24 @@ public final class MediaSessionManager {
Context mContext; Context mContext;
ContentResolver mContentResolver; ContentResolver mContentResolver;
MediaSessionManagerImplBase(Context context) { MediaSessionManagerImpl(Context context) {
mContext = context; mContext = context;
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
} }
@Override
public Context getContext() {
return mContext;
}
@Override
@SuppressWarnings("deprecation")
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) { public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
// Don't use framework's isTrustedForMediaControl().
// In P, framework's isTrustedForMediaControl() checks whether the UID, PID,
// and package name match. In MediaSession/MediaController, Context#getPackageName() is
// used by MediaController to tell MediaSession the package name.
// However, UID, PID and Context#getPackageName() may not match if a activity/service runs
// on the another app's process by specifying android:process in the AndroidManifest.xml.
// In that case, this check will always fail.
// Alternative way is to use Context#getOpPackageName() for sending the package name,
// but it's hidden so we cannot use it.
if (hasMediaControlPermission(userInfo)) {
return true;
}
try { try {
ApplicationInfo applicationInfo = ApplicationInfo applicationInfo =
mContext.getPackageManager().getApplicationInfo(userInfo.getPackageName(), 0); mContext.getPackageManager().getApplicationInfo(userInfo.getPackageName(), 0);
@ -286,6 +267,15 @@ public final class MediaSessionManager {
|| isEnabledNotificationListener(userInfo); || isEnabledNotificationListener(userInfo);
} }
/** Checks the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission. */
private boolean hasMediaControlPermission(MediaSessionManager.RemoteUserInfoImpl userInfo) {
return mContext.checkPermission(
android.Manifest.permission.MEDIA_CONTENT_CONTROL,
userInfo.getPid(),
userInfo.getUid())
== PackageManager.PERMISSION_GRANTED;
}
private boolean isPermissionGranted( private boolean isPermissionGranted(
MediaSessionManager.RemoteUserInfoImpl userInfo, String permission) { MediaSessionManager.RemoteUserInfoImpl userInfo, String permission) {
if (userInfo.getPid() < 0) { if (userInfo.getPid() < 0) {
@ -320,11 +310,12 @@ public final class MediaSessionManager {
} }
return false; return false;
} }
}
static class RemoteUserInfoImplBase implements MediaSessionManager.RemoteUserInfoImpl { private static class RemoteUserInfoImplBase implements MediaSessionManager.RemoteUserInfoImpl {
private String mPackageName; private final String mPackageName;
private int mPid; private final int mPid;
private int mUid; private final int mUid;
RemoteUserInfoImplBase(String packageName, int pid, int uid) { RemoteUserInfoImplBase(String packageName, int pid, int uid) {
mPackageName = packageName; mPackageName = packageName;
@ -371,56 +362,6 @@ public final class MediaSessionManager {
return ObjectsCompat.hash(mPackageName, mUid); return ObjectsCompat.hash(mPackageName, mUid);
} }
} }
}
@RequiresApi(21)
private static class MediaSessionManagerImplApi21 extends MediaSessionManagerImplBase {
MediaSessionManagerImplApi21(Context context) {
super(context);
mContext = context;
}
@Override
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
return hasMediaControlPermission(userInfo) || super.isTrustedForMediaControl(userInfo);
}
/** Checks the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission. */
private boolean hasMediaControlPermission(MediaSessionManager.RemoteUserInfoImpl userInfo) {
return getContext()
.checkPermission(
android.Manifest.permission.MEDIA_CONTENT_CONTROL,
userInfo.getPid(),
userInfo.getUid())
== PackageManager.PERMISSION_GRANTED;
}
}
@RequiresApi(28)
private static final class MediaSessionManagerImplApi28 extends MediaSessionManagerImplApi21 {
@Nullable android.media.session.MediaSessionManager mObject;
MediaSessionManagerImplApi28(Context context) {
super(context);
mObject =
(android.media.session.MediaSessionManager)
context.getSystemService(Context.MEDIA_SESSION_SERVICE);
}
@Override
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
// Don't use framework's isTrustedForMediaControl().
// In P, framework's isTrustedForMediaControl() checks whether the UID, PID,
// and package name match. In MediaSession/MediaController, Context#getPackageName() is
// used by MediaController to tell MediaSession the package name.
// However, UID, PID and Context#getPackageName() may not match if a activity/service runs
// on the another app's process by specifying android:process in the AndroidManifest.xml.
// In that case, this check will always fail.
// Alternative way is to use Context#getOpPackageName() for sending the package name,
// but it's hidden so we cannot use it.
return super.isTrustedForMediaControl(userInfo);
}
/** /**
* This extends {@link RemoteUserInfoImplBase} on purpose not to use frameworks' equals() and * This extends {@link RemoteUserInfoImplBase} on purpose not to use frameworks' equals() and
@ -428,28 +369,24 @@ public final class MediaSessionManager {
* *
* <p>1. To override PID checks when one of them are unknown. PID can be unknown between * <p>1. To override PID checks when one of them are unknown. PID can be unknown between
* MediaBrowserCompat / MediaBrowserServiceCompat 2. To skip checking hidden binder. Framework's * MediaBrowserCompat / MediaBrowserServiceCompat 2. To skip checking hidden binder. Framework's
* {@link android.media.session.MediaSessionManager.RemoteUserInfo} also checks internal binder * {@link android.media.session.MediaSessionManager.RemoteUserInfo} also checks internal binder to
* to distinguish multiple {@link android.media.session.MediaController} and {@link * distinguish multiple {@link android.media.session.MediaController} and {@link
* android.media.browse.MediaBrowser} in a process. However, when the binders in both * android.media.browse.MediaBrowser} in a process. However, when the binders in both
* RemoteUserInfos are {@link null}, framework's equal() specially handles the case and returns * RemoteUserInfos are {@code null}, framework's equal() specially handles the case and returns
* {@code false}. This cause two issues that we need to workaround. Issue a) RemoteUserInfos * {@code false}. This causes two issues that we need to workaround. Issue a) RemoteUserInfos
* created by key events are considered as all different. issue b) RemoteUserInfos created with * created by key events are considered as all different. Issue b) RemoteUserInfos created with
* public constructors are considers as all different. * public constructors are considered as all different.
*/ */
@RequiresApi(28) @RequiresApi(28)
private static final class RemoteUserInfoImplApi28 extends RemoteUserInfoImplBase { private static final class RemoteUserInfoImplApi28 extends RemoteUserInfoImplBase {
final android.media.session.MediaSessionManager.RemoteUserInfo mObject;
RemoteUserInfoImplApi28(String packageName, int pid, int uid) { RemoteUserInfoImplApi28(String packageName, int pid, int uid) {
super(packageName, pid, uid); super(packageName, pid, uid);
mObject =
new android.media.session.MediaSessionManager.RemoteUserInfo(packageName, pid, uid);
} }
RemoteUserInfoImplApi28( RemoteUserInfoImplApi28(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) { android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
super(remoteUserInfo.getPackageName(), remoteUserInfo.getPid(), remoteUserInfo.getUid()); super(remoteUserInfo.getPackageName(), remoteUserInfo.getPid(), remoteUserInfo.getUid());
mObject = remoteUserInfo;
} }
static String getPackageName( static String getPackageName(
@ -458,4 +395,3 @@ public final class MediaSessionManager {
} }
} }
} }
}

View File

@ -37,15 +37,6 @@ public class ParcelableVolumeInfo implements Parcelable {
public int maxVolume; public int maxVolume;
public int currentVolume; public int currentVolume;
public ParcelableVolumeInfo(
int volumeType, int audioStream, int controlType, int maxVolume, int currentVolume) {
this.volumeType = volumeType;
this.audioStream = audioStream;
this.controlType = controlType;
this.maxVolume = maxVolume;
this.currentVolume = currentVolume;
}
public ParcelableVolumeInfo(Parcel from) { public ParcelableVolumeInfo(Parcel from) {
volumeType = from.readInt(); volumeType = from.readInt();
controlType = from.readInt(); controlType = from.readInt();

View File

@ -26,7 +26,6 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.KeyEvent;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.LongDef; import androidx.annotation.LongDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -532,49 +531,6 @@ public final class PlaybackStateCompat implements Parcelable {
*/ */
public static final int ERROR_CODE_END_OF_QUEUE = 11; public static final int ERROR_CODE_END_OF_QUEUE = 11;
// KeyEvent constants only available on API 11+
private static final int KEYCODE_MEDIA_PAUSE = 127;
private static final int KEYCODE_MEDIA_PLAY = 126;
/**
* Translates a given action into a matched key code defined in {@link KeyEvent}. The given action
* should be one of the following:
*
* <ul>
* <li>{@link PlaybackStateCompat#ACTION_PLAY}
* <li>{@link PlaybackStateCompat#ACTION_PAUSE}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}
* <li>{@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}
* <li>{@link PlaybackStateCompat#ACTION_STOP}
* <li>{@link PlaybackStateCompat#ACTION_FAST_FORWARD}
* <li>{@link PlaybackStateCompat#ACTION_REWIND}
* <li>{@link PlaybackStateCompat#ACTION_PLAY_PAUSE}
* </ul>
*
* @param action The action to be translated.
* @return the key code matched to the given action.
*/
public static int toKeyCode(@MediaKeyAction long action) {
if (action == ACTION_PLAY) {
return KEYCODE_MEDIA_PLAY;
} else if (action == ACTION_PAUSE) {
return KEYCODE_MEDIA_PAUSE;
} else if (action == ACTION_SKIP_TO_NEXT) {
return KeyEvent.KEYCODE_MEDIA_NEXT;
} else if (action == ACTION_SKIP_TO_PREVIOUS) {
return KeyEvent.KEYCODE_MEDIA_PREVIOUS;
} else if (action == ACTION_STOP) {
return KeyEvent.KEYCODE_MEDIA_STOP;
} else if (action == ACTION_FAST_FORWARD) {
return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
} else if (action == ACTION_REWIND) {
return KeyEvent.KEYCODE_MEDIA_REWIND;
} else if (action == ACTION_PLAY_PAUSE) {
return KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
}
return KeyEvent.KEYCODE_UNKNOWN;
}
final int mState; final int mState;
final long mPosition; final long mPosition;
final long mBufferedPosition; final long mBufferedPosition;
@ -712,7 +668,7 @@ public final class PlaybackStateCompat implements Parcelable {
* @param timeDiff Only used for testing, otherwise it should be null. * @param timeDiff Only used for testing, otherwise it should be null.
* @return The current playback position in ms * @return The current playback position in ms
*/ */
public long getCurrentPosition(Long timeDiff) { public long getCurrentPosition(@Nullable Long timeDiff) {
long expectedPosition = long expectedPosition =
mPosition mPosition
+ (long) + (long)
@ -776,7 +732,6 @@ public final class PlaybackStateCompat implements Parcelable {
} }
/** Get the list of custom actions. */ /** Get the list of custom actions. */
@Nullable
public List<PlaybackStateCompat.CustomAction> getCustomActions() { public List<PlaybackStateCompat.CustomAction> getCustomActions() {
return mCustomActions; return mCustomActions;
} }
@ -839,20 +794,17 @@ public final class PlaybackStateCompat implements Parcelable {
/** /**
* Creates an instance from a framework {@link android.media.session.PlaybackState} object. * Creates an instance from a framework {@link android.media.session.PlaybackState} object.
* *
* <p>This method is only supported on API 21+. * @param stateFwk A {@link android.media.session.PlaybackState} object, or null if none.
*
* @param stateObj A {@link android.media.session.PlaybackState} object, or null if none.
* @return An equivalent {@link PlaybackStateCompat} object, or null if none. * @return An equivalent {@link PlaybackStateCompat} object, or null if none.
*/ */
@Nullable @Nullable
public static PlaybackStateCompat fromPlaybackState(@Nullable Object stateObj) { public static PlaybackStateCompat fromPlaybackState(@Nullable PlaybackState stateFwk) {
if (stateObj != null && Build.VERSION.SDK_INT >= 21) { if (stateFwk != null) {
PlaybackState stateFwk = (PlaybackState) stateObj; List<PlaybackState.CustomAction> customActionFwks = stateFwk.getCustomActions();
List<PlaybackState.CustomAction> customActionFwks = Api21Impl.getCustomActions(stateFwk);
List<PlaybackStateCompat.CustomAction> customActions = null; List<PlaybackStateCompat.CustomAction> customActions = null;
if (customActionFwks != null) { if (customActionFwks != null) {
customActions = new ArrayList<>(customActionFwks.size()); customActions = new ArrayList<>(customActionFwks.size());
for (Object customActionFwk : customActionFwks) { for (PlaybackState.CustomAction customActionFwk : customActionFwks) {
if (customActionFwk == null) { if (customActionFwk == null) {
continue; continue;
} }
@ -868,16 +820,16 @@ public final class PlaybackStateCompat implements Parcelable {
} }
PlaybackStateCompat stateCompat = PlaybackStateCompat stateCompat =
new PlaybackStateCompat( new PlaybackStateCompat(
Api21Impl.getState(stateFwk), stateFwk.getState(),
Api21Impl.getPosition(stateFwk), stateFwk.getPosition(),
Api21Impl.getBufferedPosition(stateFwk), stateFwk.getBufferedPosition(),
Api21Impl.getPlaybackSpeed(stateFwk), stateFwk.getPlaybackSpeed(),
Api21Impl.getActions(stateFwk), stateFwk.getActions(),
ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_UNKNOWN_ERROR,
Api21Impl.getErrorMessage(stateFwk), stateFwk.getErrorMessage(),
Api21Impl.getLastPositionUpdateTime(stateFwk), stateFwk.getLastPositionUpdateTime(),
customActions, customActions,
Api21Impl.getActiveQueueItemId(stateFwk), stateFwk.getActiveQueueItemId(),
extras); extras);
stateCompat.mStateFwk = stateFwk; stateCompat.mStateFwk = stateFwk;
return stateCompat; return stateCompat;
@ -889,30 +841,28 @@ public final class PlaybackStateCompat implements Parcelable {
/** /**
* Gets the underlying framework {@link android.media.session.PlaybackState} object. * Gets the underlying framework {@link android.media.session.PlaybackState} object.
* *
* <p>This method is only supported on API 21+. * @return An equivalent {@link android.media.session.PlaybackState} object.
*
* @return An equivalent {@link android.media.session.PlaybackState} object, or null if none.
*/ */
@Nullable @SuppressWarnings("argument.type.incompatible") // setErrorMessage not annotated as nullable
public Object getPlaybackState() { public PlaybackState getPlaybackState() {
if (mStateFwk == null && Build.VERSION.SDK_INT >= 21) { if (mStateFwk == null) {
PlaybackState.Builder builder = Api21Impl.createBuilder(); PlaybackState.Builder builder = new PlaybackState.Builder();
Api21Impl.setState(builder, mState, mPosition, mSpeed, mUpdateTime); builder.setState(mState, mPosition, mSpeed, mUpdateTime);
Api21Impl.setBufferedPosition(builder, mBufferedPosition); builder.setBufferedPosition(mBufferedPosition);
Api21Impl.setActions(builder, mActions); builder.setActions(mActions);
Api21Impl.setErrorMessage(builder, mErrorMessage); builder.setErrorMessage(mErrorMessage);
for (PlaybackStateCompat.CustomAction customAction : mCustomActions) { for (PlaybackStateCompat.CustomAction customAction : mCustomActions) {
PlaybackState.CustomAction action = PlaybackState.CustomAction action =
(PlaybackState.CustomAction) customAction.getCustomAction(); (PlaybackState.CustomAction) customAction.getCustomAction();
if (action != null) { if (action != null) {
Api21Impl.addCustomAction(builder, action); builder.addCustomAction(action);
} }
} }
Api21Impl.setActiveQueueItemId(builder, mActiveItemId); builder.setActiveQueueItemId(mActiveItemId);
if (Build.VERSION.SDK_INT >= 22) { if (Build.VERSION.SDK_INT >= 22) {
Api22Impl.setExtras(builder, mExtras); Api22Impl.setExtras(builder, mExtras);
} }
mStateFwk = Api21Impl.build(builder); mStateFwk = builder.build();
} }
return mStateFwk; return mStateFwk;
} }
@ -975,22 +925,19 @@ public final class PlaybackStateCompat implements Parcelable {
* Creates an instance from a framework {@link android.media.session.PlaybackState.CustomAction} * Creates an instance from a framework {@link android.media.session.PlaybackState.CustomAction}
* object. * object.
* *
* <p>This method is only supported on API 21+.
*
* @param customActionObj A {@link android.media.session.PlaybackState.CustomAction} object, or * @param customActionObj A {@link android.media.session.PlaybackState.CustomAction} object, or
* null if none. * null if none.
* @return An equivalent {@link PlaybackStateCompat.CustomAction} object, or null if none. * @return An equivalent {@link PlaybackStateCompat.CustomAction} object, or null if none.
*/ */
@RequiresApi(21)
public static PlaybackStateCompat.CustomAction fromCustomAction(Object customActionObj) { public static PlaybackStateCompat.CustomAction fromCustomAction(Object customActionObj) {
PlaybackState.CustomAction customActionFwk = (PlaybackState.CustomAction) customActionObj; PlaybackState.CustomAction customActionFwk = (PlaybackState.CustomAction) customActionObj;
Bundle extras = Api21Impl.getExtras(customActionFwk); Bundle extras = customActionFwk.getExtras();
MediaSessionCompat.ensureClassLoader(extras); MediaSessionCompat.ensureClassLoader(extras);
PlaybackStateCompat.CustomAction customActionCompat = PlaybackStateCompat.CustomAction customActionCompat =
new PlaybackStateCompat.CustomAction( new PlaybackStateCompat.CustomAction(
Api21Impl.getAction(customActionFwk), customActionFwk.getAction(),
Api21Impl.getName(customActionFwk), customActionFwk.getName(),
Api21Impl.getIcon(customActionFwk), customActionFwk.getIcon(),
extras); extras);
customActionCompat.mCustomActionFwk = customActionFwk; customActionCompat.mCustomActionFwk = customActionFwk;
return customActionCompat; return customActionCompat;
@ -1000,21 +947,20 @@ public final class PlaybackStateCompat implements Parcelable {
* Gets the underlying framework {@link android.media.session.PlaybackState.CustomAction} * Gets the underlying framework {@link android.media.session.PlaybackState.CustomAction}
* object. * object.
* *
* <p>This method is only supported on API 21+.
*
* @return An equivalent {@link android.media.session.PlaybackState.CustomAction} object, or * @return An equivalent {@link android.media.session.PlaybackState.CustomAction} object, or
* null if none. * null if none.
*/ */
@SuppressWarnings("argument.type.incompatible") // setExtras not annotated as nullable
@Nullable @Nullable
public Object getCustomAction() { public Object getCustomAction() {
if (mCustomActionFwk != null || Build.VERSION.SDK_INT < 21) { if (mCustomActionFwk != null) {
return mCustomActionFwk; return mCustomActionFwk;
} }
PlaybackState.CustomAction.Builder builder = PlaybackState.CustomAction.Builder builder =
Api21Impl.createCustomActionBuilder(mAction, mName, mIcon); new PlaybackState.CustomAction.Builder(mAction, mName, mIcon);
Api21Impl.setExtras(builder, mExtras); builder.setExtras(mExtras);
return Api21Impl.build(builder); return builder.build();
} }
public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR = public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR =
@ -1325,10 +1271,6 @@ public final class PlaybackStateCompat implements Parcelable {
* @return this * @return this
*/ */
public Builder addCustomAction(PlaybackStateCompat.CustomAction customAction) { public Builder addCustomAction(PlaybackStateCompat.CustomAction customAction) {
if (customAction == null) {
throw new IllegalArgumentException(
"You may not add a null CustomAction to PlaybackStateCompat");
}
mCustomActions.add(customAction); mCustomActions.add(customAction);
return this; return this;
} }
@ -1400,118 +1342,6 @@ public final class PlaybackStateCompat implements Parcelable {
} }
} }
@RequiresApi(21)
private static class Api21Impl {
private Api21Impl() {}
static PlaybackState.Builder createBuilder() {
return new PlaybackState.Builder();
}
static void setState(
PlaybackState.Builder builder,
int state,
long position,
float playbackSpeed,
long updateTime) {
builder.setState(state, position, playbackSpeed, updateTime);
}
static void setBufferedPosition(PlaybackState.Builder builder, long bufferedPosition) {
builder.setBufferedPosition(bufferedPosition);
}
static void setActions(PlaybackState.Builder builder, long actions) {
builder.setActions(actions);
}
@SuppressWarnings("argument.type.incompatible") // Platform class not annotated as nullable
static void setErrorMessage(PlaybackState.Builder builder, @Nullable CharSequence error) {
builder.setErrorMessage(error);
}
static void addCustomAction(
PlaybackState.Builder builder, PlaybackState.CustomAction customAction) {
builder.addCustomAction(customAction);
}
static void setActiveQueueItemId(PlaybackState.Builder builder, long id) {
builder.setActiveQueueItemId(id);
}
static List<PlaybackState.CustomAction> getCustomActions(PlaybackState state) {
return state.getCustomActions();
}
static PlaybackState build(PlaybackState.Builder builder) {
return builder.build();
}
static int getState(PlaybackState state) {
return state.getState();
}
static long getPosition(PlaybackState state) {
return state.getPosition();
}
static long getBufferedPosition(PlaybackState state) {
return state.getBufferedPosition();
}
static float getPlaybackSpeed(PlaybackState state) {
return state.getPlaybackSpeed();
}
static long getActions(PlaybackState state) {
return state.getActions();
}
@Nullable
static CharSequence getErrorMessage(PlaybackState state) {
return state.getErrorMessage();
}
static long getLastPositionUpdateTime(PlaybackState state) {
return state.getLastPositionUpdateTime();
}
static long getActiveQueueItemId(PlaybackState state) {
return state.getActiveQueueItemId();
}
static PlaybackState.CustomAction.Builder createCustomActionBuilder(
String action, CharSequence name, int icon) {
return new PlaybackState.CustomAction.Builder(action, name, icon);
}
@SuppressWarnings("argument.type.incompatible") // Platform class not annotated as nullable
static void setExtras(PlaybackState.CustomAction.Builder builder, @Nullable Bundle extras) {
builder.setExtras(extras);
}
static PlaybackState.CustomAction build(PlaybackState.CustomAction.Builder builder) {
return builder.build();
}
@Nullable
static Bundle getExtras(PlaybackState.CustomAction customAction) {
return customAction.getExtras();
}
static String getAction(PlaybackState.CustomAction customAction) {
return customAction.getAction();
}
static CharSequence getName(PlaybackState.CustomAction customAction) {
return customAction.getName();
}
static int getIcon(PlaybackState.CustomAction customAction) {
return customAction.getIcon();
}
}
@RequiresApi(22) @RequiresApi(22)
private static class Api22Impl { private static class Api22Impl {
private Api22Impl() {} private Api22Impl() {}

View File

@ -21,7 +21,6 @@ import android.media.VolumeProvider;
import android.os.Build; import android.os.Build;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -96,25 +95,6 @@ public abstract class VolumeProviderCompat {
mControlId = volumeControlId; mControlId = volumeControlId;
} }
/**
* Get the current volume of the provider.
*
* @return The current volume.
*/
public final int getCurrentVolume() {
return mCurrentVolume;
}
/**
* Get the volume control type that this volume provider uses.
*
* @return The volume control type for this volume provider
*/
@ControlType
public final int getVolumeControl() {
return mControlType;
}
/** /**
* Get the maximum volume this provider allows. * Get the maximum volume this provider allows.
* *
@ -131,26 +111,13 @@ public abstract class VolumeProviderCompat {
*/ */
public final void setCurrentVolume(int currentVolume) { public final void setCurrentVolume(int currentVolume) {
mCurrentVolume = currentVolume; mCurrentVolume = currentVolume;
if (Build.VERSION.SDK_INT >= 21) {
VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider(); VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider();
Api21Impl.setCurrentVolume(volumeProviderFwk, currentVolume); volumeProviderFwk.setCurrentVolume(currentVolume);
}
if (mCallback != null) { if (mCallback != null) {
mCallback.onVolumeChanged(this); mCallback.onVolumeChanged(this);
} }
} }
/**
* Gets the volume control ID. It can be used to identify which volume provider is used by the
* session.
*
* @return the volume control ID or {@code null} if it isn't set.
*/
@Nullable
public final String getVolumeControlId() {
return mControlId;
}
/** /**
* Override to handle requests to set the volume of the current output. * Override to handle requests to set the volume of the current output.
* *
@ -181,7 +148,6 @@ public abstract class VolumeProviderCompat {
* *
* @return An equivalent {@link android.media.VolumeProvider} object, or null if none. * @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
*/ */
@RequiresApi(21)
public Object getVolumeProvider() { public Object getVolumeProvider() {
if (mVolumeProviderFwk == null) { if (mVolumeProviderFwk == null) {
if (Build.VERSION.SDK_INT >= 30) { if (Build.VERSION.SDK_INT >= 30) {
@ -219,13 +185,4 @@ public abstract class VolumeProviderCompat {
public abstract static class Callback { public abstract static class Callback {
public abstract void onVolumeChanged(VolumeProviderCompat volumeProvider); public abstract void onVolumeChanged(VolumeProviderCompat volumeProvider);
} }
@RequiresApi(21)
private static class Api21Impl {
private Api21Impl() {}
static void setCurrentVolume(VolumeProvider volumeProvider, int currentVolume) {
volumeProvider.setCurrentVolume(currentVolume);
}
}
} }

View File

@ -266,77 +266,6 @@ public final class LegacyConversionsTest {
assertThat(convertedMediaItemWithDisplayTitleAndTitle.mediaMetadata.albumTitle).isNull(); assertThat(convertedMediaItemWithDisplayTitleAndTitle.mediaMetadata.albumTitle).isNull();
} }
@Test
public void convertToMediaMetadataCompat_displayTitleAndTitleHandledCorrectly() {
MediaMetadata mediaMetadataWithTitleOnly =
new MediaMetadata.Builder()
.setTitle("title")
.setSubtitle("subtitle")
.setDescription("description")
.setArtist("artist")
.setAlbumArtist("albumArtist")
.build();
MediaMetadata mediaMetadataWithDisplayTitleOnly =
new MediaMetadata.Builder()
.setDisplayTitle("displayTitle")
.setSubtitle("subtitle")
.setDescription("description")
.setArtist("artist")
.setAlbumArtist("albumArtist")
.build();
MediaMetadata mediaMetadataWithDisplayTitleAndTitle =
new MediaMetadata.Builder()
.setTitle("title")
.setDisplayTitle("displayTitle")
.setSubtitle("subtitle")
.setDescription("description")
.setArtist("artist")
.setAlbumArtist("albumArtist")
.build();
MediaDescriptionCompat mediaDescriptionCompatFromDisplayTitleAndTitle =
LegacyConversions.convertToMediaMetadataCompat(
mediaMetadataWithDisplayTitleAndTitle,
"mediaId",
/* mediaUri= */ null,
/* durationMs= */ 10_000L,
/* artworkBitmap= */ null)
.getDescription();
MediaDescriptionCompat mediaDescriptionCompatFromDisplayTitleOnly =
LegacyConversions.convertToMediaMetadataCompat(
mediaMetadataWithDisplayTitleOnly,
"mediaId",
/* mediaUri= */ null,
/* durationMs= */ 10_000L,
/* artworkBitmap= */ null)
.getDescription();
MediaDescriptionCompat mediaDescriptionCompatFromTitleOnly =
LegacyConversions.convertToMediaMetadataCompat(
mediaMetadataWithTitleOnly,
"mediaId",
/* mediaUri= */ null,
/* durationMs= */ 10_000L,
/* artworkBitmap= */ null)
.getDescription();
assertThat(mediaDescriptionCompatFromDisplayTitleAndTitle.getTitle().toString())
.isEqualTo("displayTitle");
assertThat(mediaDescriptionCompatFromDisplayTitleAndTitle.getSubtitle().toString())
.isEqualTo("subtitle");
assertThat(mediaDescriptionCompatFromDisplayTitleAndTitle.getDescription().toString())
.isEqualTo("description");
assertThat(mediaDescriptionCompatFromDisplayTitleOnly.getTitle().toString())
.isEqualTo("displayTitle");
assertThat(mediaDescriptionCompatFromDisplayTitleOnly.getSubtitle().toString())
.isEqualTo("subtitle");
assertThat(mediaDescriptionCompatFromDisplayTitleOnly.getDescription().toString())
.isEqualTo("description");
assertThat(mediaDescriptionCompatFromTitleOnly.getTitle().toString()).isEqualTo("title");
assertThat(mediaDescriptionCompatFromTitleOnly.getSubtitle().toString()).isEqualTo("artist");
assertThat(mediaDescriptionCompatFromTitleOnly.getDescription().toString())
.isEqualTo("albumArtist");
}
@Test @Test
public void convertToQueueItemId() { public void convertToQueueItemId() {
assertThat(LegacyConversions.convertToQueueItemId(C.INDEX_UNSET)) assertThat(LegacyConversions.convertToQueueItemId(C.INDEX_UNSET))

View File

@ -21,10 +21,7 @@ import static org.hamcrest.core.IsNot.not;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -32,55 +29,45 @@ import org.junit.runner.RunWith;
/** Test {@link AudioAttributesCompat}. */ /** Test {@link AudioAttributesCompat}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AudioAttributesCompatTest { public class AudioAttributesCompatTest {
// some macros for conciseness
static AudioAttributesCompat.Builder mkBuilder(
@AudioAttributesCompat.AttributeContentType int type,
@AudioAttributesCompat.AttributeUsage int usage) {
return new AudioAttributesCompat.Builder().setContentType(type).setUsage(usage);
}
static AudioAttributesCompat.Builder mkBuilder(int legacyStream) { private Object mMediaAA;
return new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream); private AudioAttributesCompat mMediaAAC;
} private AudioAttributesCompat mMediaLegacyAAC;
private AudioAttributesCompat mMediaAACFromAA;
// some objects we'll toss around private AudioAttributesCompat mNotificationAAC;
Object mMediaAA; private AudioAttributesCompat mNotificationLegacyAAC;
AudioAttributesCompat mMediaAAC;
AudioAttributesCompat mMediaLegacyAAC;
AudioAttributesCompat mMediaAACFromAA;
AudioAttributesCompat mNotificationAAC;
AudioAttributesCompat mNotificationLegacyAAC;
@Before @Before
@SdkSuppress(minSdkVersion = 21)
public void setUpApi21() { public void setUpApi21() {
if (Build.VERSION.SDK_INT < 21) {
return;
}
mMediaAA = mMediaAA =
new AudioAttributes.Builder() new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA) .setUsage(AudioAttributes.USAGE_MEDIA)
.build(); .build();
mMediaAACFromAA = AudioAttributesCompat.wrap((AudioAttributes) mMediaAA); mMediaAACFromAA = AudioAttributesCompat.wrap(mMediaAA);
} }
@Before @Before
public void setUp() { public void setUp() {
mMediaAAC = mMediaAAC =
mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC, AudioAttributesCompat.USAGE_MEDIA) new AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
.build(); .build();
mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build(); mMediaLegacyAAC =
new AudioAttributesCompat.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
mNotificationAAC = mNotificationAAC =
mkBuilder( new AudioAttributesCompat.Builder()
AudioAttributesCompat.CONTENT_TYPE_SONIFICATION, .setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION)
AudioAttributesCompat.USAGE_NOTIFICATION) .setUsage(AudioAttributesCompat.USAGE_NOTIFICATION)
.build();
mNotificationLegacyAAC =
new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build(); .build();
mNotificationLegacyAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
} }
@Test @Test
@SdkSuppress(minSdkVersion = 21)
public void testCreateWithAudioAttributesApi21() { public void testCreateWithAudioAttributesApi21() {
assertThat(mMediaAACFromAA, not(equalTo(null))); assertThat(mMediaAACFromAA, not(equalTo(null)));
assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA)); assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA));
@ -90,7 +77,6 @@ public class AudioAttributesCompatTest {
} }
@Test @Test
@SdkSuppress(minSdkVersion = 21)
public void testEqualityApi21() { public void testEqualityApi21() {
assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA)); assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC))); assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
@ -126,59 +112,35 @@ public class AudioAttributesCompatTest {
} }
@Test @Test
@SdkSuppress(minSdkVersion = 21)
public void testLegacyStreamTypeInferenceApi21() { public void testLegacyStreamTypeInferenceApi21() {
assertThat(mMediaAACFromAA.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC)); assertThat(mMediaAACFromAA.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
} }
@Test
public void testLegacyStreamTypeInferenceInLegacyMode() {
// the builders behave differently based on the value of this only-for-testing global
// so we need our very own objects inside this method
AudioAttributesCompat.setForceLegacyBehavior(true);
AudioAttributesCompat mediaAAC =
mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC, AudioAttributesCompat.USAGE_MEDIA)
.build();
AudioAttributesCompat mediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
AudioAttributesCompat notificationAAC =
mkBuilder(
AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
AudioAttributesCompat.USAGE_NOTIFICATION)
.build();
AudioAttributesCompat notificationLegacyAAC =
mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
assertThat(mediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
assertThat(mediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
assertThat(notificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
assertThat(
notificationLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
}
@Test @Test
public void testUsageAndContentTypeInferredFromLegacyStreamType() { public void testUsageAndContentTypeInferredFromLegacyStreamType() {
AudioAttributesCompat alarmAAC = mkBuilder(AudioManager.STREAM_ALARM).build(); AudioAttributesCompat alarmAAC =
new AudioAttributesCompat.Builder().setLegacyStreamType(AudioManager.STREAM_ALARM).build();
assertThat(alarmAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_ALARM)); assertThat(alarmAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_ALARM));
assertThat(alarmAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION)); assertThat(alarmAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION));
AudioAttributesCompat musicAAC = mkBuilder(AudioManager.STREAM_MUSIC).build(); AudioAttributesCompat musicAAC =
new AudioAttributesCompat.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
assertThat(musicAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA)); assertThat(musicAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA));
assertThat(musicAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC)); assertThat(musicAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC));
AudioAttributesCompat notificationAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build(); AudioAttributesCompat notificationAAC =
new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build();
assertThat(notificationAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_NOTIFICATION)); assertThat(notificationAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_NOTIFICATION));
assertThat( assertThat(
notificationAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION)); notificationAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION));
AudioAttributesCompat voiceCallAAC = mkBuilder(AudioManager.STREAM_VOICE_CALL).build(); AudioAttributesCompat voiceCallAAC =
new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
.build();
assertThat(voiceCallAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)); assertThat(voiceCallAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION));
assertThat(voiceCallAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SPEECH)); assertThat(voiceCallAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SPEECH));
} }
@After
public void cleanUp() {
AudioAttributesCompat.setForceLegacyBehavior(false);
}
} }

View File

@ -22,7 +22,6 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.media3.test.session.common.TestUtils; import androidx.media3.test.session.common.TestUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -30,7 +29,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class MediaDescriptionCompatTest { public class MediaDescriptionCompatTest {
@SdkSuppress(minSdkVersion = 21)
@Test @Test
public void roundTripViaFrameworkObject_returnsEqualMediaUriAndExtras() { public void roundTripViaFrameworkObject_returnsEqualMediaUriAndExtras() {
Uri mediaUri = Uri.parse("androidx://media/uri"); Uri mediaUri = Uri.parse("androidx://media/uri");
@ -54,7 +52,6 @@ public class MediaDescriptionCompatTest {
TestUtils.equals(createExtras(), restoredDescription2.getExtras()); TestUtils.equals(createExtras(), restoredDescription2.getExtras());
} }
@SdkSuppress(minSdkVersion = 21)
@Test @Test
public void getMediaDescription_withMediaUri_doesNotTouchExtras() { public void getMediaDescription_withMediaUri_doesNotTouchExtras() {
MediaDescriptionCompat originalDescription = MediaDescriptionCompat originalDescription =