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";
// 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 =
ImmutableSet.of(
MediaMetadataCompat.METADATA_KEY_TITLE,
@ -1476,13 +1472,11 @@ import java.util.concurrent.TimeoutException;
if (state != null) {
List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions();
if (customActions != null) {
for (CustomAction customAction : customActions) {
String action = customAction.getAction();
@Nullable Bundle extras = customAction.getExtras();
sessionCommandsBuilder.add(
new SessionCommand(action, extras == null ? Bundle.EMPTY : extras));
}
for (CustomAction customAction : customActions) {
String action = customAction.getAction();
@Nullable Bundle extras = customAction.getExtras();
sessionCommandsBuilder.add(
new SessionCommand(action, extras == null ? Bundle.EMPTY : extras));
}
}
return sessionCommandsBuilder.build();
@ -1504,9 +1498,6 @@ import java.util.concurrent.TimeoutException;
return ImmutableList.of();
}
List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions();
if (customActions == null) {
return ImmutableList.of();
}
ImmutableList.Builder<CommandButton> customLayout = new ImmutableList.Builder<>();
for (CustomAction customAction : customActions) {
String action = customAction.getAction();

View File

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

View File

@ -450,7 +450,7 @@ public final class SessionToken {
private static MediaSessionCompat.Token createCompatToken(
Parcelable platformOrLegacyCompatToken) {
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.
return LegacyParcelableUtil.convert(

View File

@ -27,12 +27,9 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
/**
@ -68,7 +65,6 @@ import java.util.Objects;
@UnstableApi
@RestrictTo(LIBRARY)
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. */
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 SparseIntArray SUPPRESSIBLE_USAGES;
// used by tests
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean sForceLegacyBehavior;
static {
SUPPRESSIBLE_USAGES = new SparseIntArray();
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. */
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_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
public final AudioAttributesImpl mImpl;
private final AudioAttributesImpl mImpl;
AudioAttributesCompat(AudioAttributesImpl 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
/**
@ -301,17 +250,12 @@ public class AudioAttributesCompat {
* @param aa an instance of {@link AudioAttributes}.
* @return the new <code>AudioAttributesCompat</code>, or <code>null</code> on API &lt; 21
*/
@Nullable
public static AudioAttributesCompat wrap(Object aa) {
if (sForceLegacyBehavior) {
return null;
}
if (Build.VERSION.SDK_INT >= 26) {
return new AudioAttributesCompat(new AudioAttributesImplApi26((AudioAttributes) aa));
} else if (Build.VERSION.SDK_INT >= 21) {
} else {
return new AudioAttributesCompat(new AudioAttributesImplApi21((AudioAttributes) aa));
}
return null;
}
// 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.
*/
public Builder() {
if (sForceLegacyBehavior) {
mBuilderImpl = new AudioAttributesImplBase.Builder();
} else if (Build.VERSION.SDK_INT >= 26) {
if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplApi26.Builder();
} else if (Build.VERSION.SDK_INT >= 21) {
mBuilderImpl = new AudioAttributesImplApi21.Builder();
} 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.
*/
public Builder(AudioAttributesCompat aa) {
if (sForceLegacyBehavior) {
mBuilderImpl = new AudioAttributesImplBase.Builder(aa);
} else if (Build.VERSION.SDK_INT >= 26) {
if (Build.VERSION.SDK_INT >= 26) {
mBuilderImpl = new AudioAttributesImplApi26.Builder(checkNotNull(aa.unwrap()));
} else if (Build.VERSION.SDK_INT >= 21) {
mBuilderImpl = new AudioAttributesImplApi21.Builder(checkNotNull(aa.unwrap()));
} 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.
*
* @param flags a combination of {@link AudioAttributesCompat#FLAG_AUDIBILITY_ENFORCED}, {@link
* AudioAttributesCompat#FLAG_HW_AV_SYNC}.
* @return the same Builder instance.
* @param flags The optional flag of {@link AudioAttributesCompat#FLAG_AUDIBILITY_ENFORCED}.
* @return The same Builder instance.
*/
public Builder setFlags(int flags) {
mBuilderImpl.setFlags(flags);
@ -498,92 +433,31 @@ public class AudioAttributesCompat {
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 {
public static final int STREAM_BLUETOOTH_SCO = 6;
public static final int STREAM_SYSTEM_ENFORCED = 7;
public static final int STREAM_TTS = 9;
public static final int STREAM_ACCESSIBILITY = 10;
private AudioManagerHidden() {}
}
/** Prevent AudioAttributes from being used even on platforms that support it. */
public static void setForceLegacyBehavior(boolean force) {
sForceLegacyBehavior = force;
}
int getRawLegacyStreamType() {
return mImpl.getRawLegacyStreamType();
}
static int toVolumeStreamType(
boolean fromGetVolumeControlStream, int flags, @AttributeUsage int usage) {
static int toVolumeStreamType(int flags, @AttributeUsage int usage) {
// flags to stream type mapping
if ((flags & FLAG_AUDIBILITY_ENFORCED) == FLAG_AUDIBILITY_ENFORCED) {
return fromGetVolumeControlStream
? AudioManager.STREAM_SYSTEM
: AudioManagerHidden.STREAM_SYSTEM_ENFORCED;
return AudioManagerHidden.STREAM_SYSTEM_ENFORCED;
}
if ((flags & FLAG_SCO) == FLAG_SCO) {
return fromGetVolumeControlStream
? AudioManager.STREAM_VOICE_CALL
: AudioManagerHidden.STREAM_BLUETOOTH_SCO;
return AudioManagerHidden.STREAM_BLUETOOTH_SCO;
}
// usage to stream type mapping
switch (usage) {
case USAGE_MEDIA:
case USAGE_GAME:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANT:
return AudioManager.STREAM_MUSIC;
case USAGE_ASSISTANCE_SONIFICATION:
return AudioManager.STREAM_SYSTEM;
case USAGE_VOICE_COMMUNICATION:
return AudioManager.STREAM_VOICE_CALL;
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return fromGetVolumeControlStream
? AudioManager.STREAM_VOICE_CALL
: AudioManager.STREAM_DTMF;
return AudioManager.STREAM_DTMF;
case USAGE_ALARM:
return AudioManager.STREAM_ALARM;
case USAGE_NOTIFICATION_RINGTONE:
@ -596,15 +470,8 @@ public class AudioAttributesCompat {
return AudioManager.STREAM_NOTIFICATION;
case USAGE_ASSISTANCE_ACCESSIBILITY:
return AudioManagerHidden.STREAM_ACCESSIBILITY;
case USAGE_UNKNOWN:
return AudioManager.STREAM_MUSIC;
default:
if (fromGetVolumeControlStream) {
throw new IllegalArgumentException(
"Unknown usage value " + usage + " in audio attributes");
} else {
return AudioManager.STREAM_MUSIC;
}
return AudioManager.STREAM_MUSIC;
}
}
@ -613,10 +480,7 @@ public class AudioAttributesCompat {
if (!(o instanceof AudioAttributesCompat)) {
return false;
}
final AudioAttributesCompat that = (AudioAttributesCompat) o;
if (this.mImpl == null) {
return that.mImpl == null;
}
AudioAttributesCompat that = (AudioAttributesCompat) o;
return this.mImpl.equals(that.mImpl);
}
@ -640,7 +504,7 @@ public class AudioAttributesCompat {
USAGE_VIRTUAL_SOURCE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {}
private @interface AttributeUsage {}
@IntDef({
CONTENT_TYPE_UNKNOWN,
@ -650,20 +514,15 @@ public class AudioAttributesCompat {
CONTENT_TYPE_SONIFICATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeContentType {}
private @interface AttributeContentType {}
public interface AudioAttributesImpl {
private interface AudioAttributesImpl {
/** Gets framework {@link android.media.AudioAttributes}. */
@Nullable
Object getAudioAttributes();
int getVolumeControlStream();
int getLegacyStreamType();
// Returns explicitly set legacy stream type.
int getRawLegacyStreamType();
int getContentType();
@AudioAttributesCompat.AttributeUsage
@ -685,272 +544,11 @@ public class AudioAttributesCompat {
}
}
public static class AudioAttributesImplBase 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 {
private static class AudioAttributesImplApi21 implements AudioAttributesImpl {
@Nullable public AudioAttributes mAudioAttributes;
public int mLegacyStreamType = INVALID_STREAM_TYPE;
public AudioAttributesImplApi21() {}
public final int mLegacyStreamType;
AudioAttributesImplApi21(AudioAttributes audioAttributes) {
this(audioAttributes, INVALID_STREAM_TYPE);
@ -967,23 +565,12 @@ public class AudioAttributesCompat {
return mAudioAttributes;
}
@Override
public int getVolumeControlStream() {
// TODO: address the framework change ag/4995785.
return AudioAttributesCompat.toVolumeStreamType(true, getFlags(), getUsage());
}
@Override
public int getLegacyStreamType() {
if (mLegacyStreamType != INVALID_STREAM_TYPE) {
return mLegacyStreamType;
}
return AudioAttributesCompat.toVolumeStreamType(false, getFlags(), getUsage());
}
@Override
public int getRawLegacyStreamType() {
return mLegacyStreamType;
return AudioAttributesCompat.toVolumeStreamType(getFlags(), getUsage());
}
@Override
@ -1020,7 +607,6 @@ public class AudioAttributesCompat {
return "AudioAttributesCompat: audioattributes=" + mAudioAttributes;
}
@RequiresApi(21)
static class Builder implements AudioAttributesImpl.Builder {
final AudioAttributes.Builder mFwkBuilder;
@ -1069,19 +655,12 @@ public class AudioAttributesCompat {
}
@RequiresApi(26)
public static class AudioAttributesImplApi26 extends AudioAttributesImplApi21 {
public AudioAttributesImplApi26() {}
private static class AudioAttributesImplApi26 extends AudioAttributesImplApi21 {
AudioAttributesImplApi26(AudioAttributes audioAttributes) {
super(audioAttributes, INVALID_STREAM_TYPE);
}
@Override
public int getVolumeControlStream() {
return checkNotNull(mAudioAttributes).getVolumeControlStream();
}
@RequiresApi(26)
static class Builder extends AudioAttributesImplApi21.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_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
* 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.pm.PackageManager;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@ -130,8 +129,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
static final String TAG = "MBServiceCompat";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final float EPSILON = 0.00001f;
private @MonotonicNonNull MediaBrowserServiceImpl mImpl;
/** The {@link Intent} that must be declared as handled by the service. */
@ -176,7 +173,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@SuppressWarnings({
"argument.type.incompatible",
"assignment.type.incompatible"
}) // Using this before construtor completes
}) // Using this before constructor completes
final ServiceHandler mHandler = new ServiceHandler(/* service= */ this);
@Nullable MediaSessionCompat.Token mSession;
@ -199,115 +196,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
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 {
final List<Bundle> mRootExtrasList = new ArrayList<>();
@MonotonicNonNull MediaBrowserService mServiceFwk;
@ -345,8 +233,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
mRootExtrasList.clear();
}
checkNotNull(mServiceFwk)
.setSessionToken(checkNotNull((MediaSession.Token) token.getToken()));
checkNotNull(mServiceFwk).setSessionToken(token.getToken());
}
@Override
@ -520,7 +407,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
return mCurConnection.browserInfo;
}
@RequiresApi(21)
class MediaBrowserServiceApi21 extends MediaBrowserService {
@SuppressWarnings("method.invocation.invalid") // Calling base method from constructor
MediaBrowserServiceApi21(Context context) {
@ -543,8 +429,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
MediaBrowserServiceImplApi21.this.onLoadChildren(
parentId, new ResultWrapper<List<Parcel>>(result));
MediaBrowserServiceImplApi21.this.onLoadChildren(parentId, new ResultWrapper<>(result));
}
}
}
@ -588,7 +473,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
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);
mCurConnection = mConnectionFromFwk;
MediaBrowserServiceImplApi26.this.onLoadChildren(
parentId, new ResultWrapper<List<Parcel>>(result), options);
parentId, new ResultWrapper<>(result), options);
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}
* 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#onLoadItem
* @see MediaBrowserServiceCompat#onSearch
@ -826,23 +707,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
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
* #onCustomAction}.
@ -897,32 +761,12 @@ public abstract class MediaBrowserServiceCompat extends Service {
*/
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.
*/
void onErrorSent(@Nullable Bundle extras) {
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 {
@ -968,7 +812,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
mConnections.put(b, connection);
b.linkToDeath(connection, 0);
if (mSession != null) {
callbacks.onConnect(root.getRootId(), mSession, root.getExtras());
callbacks.onConnect(root.getRootId(), checkNotNull(mSession), root.getExtras());
}
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. " + "pkg=" + pkg);
@ -1188,7 +1032,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
private interface ServiceCallbacks {
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;
void onConnectFailed() throws RemoteException;
@ -1214,8 +1058,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
@Override
public void onConnect(
String root, @Nullable MediaSessionCompat.Token session, @Nullable Bundle extras)
public void onConnect(String root, MediaSessionCompat.Token session, @Nullable Bundle extras)
throws RemoteException {
if (extras == null) {
extras = new Bundle();
@ -1236,7 +1079,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void onLoadChildren(
@Nullable String mediaId,
@ -1268,7 +1110,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
}
@RequiresApi(21)
@SuppressWarnings({"rawtypes", "unchecked"})
static class ResultWrapper<T> {
MediaBrowserService.Result mResultFwk;
@ -1331,10 +1172,8 @@ public abstract class MediaBrowserServiceCompat extends Service {
mImpl = new MediaBrowserServiceImplApi26();
} else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();
} else {
mImpl = new MediaBrowserServiceImplBase();
mImpl = new MediaBrowserServiceImplApi21();
}
mImpl.onCreate();
}
@ -1437,7 +1276,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
/**
* Called when a {@link MediaBrowserCompat#unsubscribe} is called.
*
* @param id
* @param id The id to unsubscribe.
*/
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
* 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
* custom action is completed. Implementation can also call {@link Result#sendProgressUpdate} to
* send an interim update to the requester.
* custom action is completed.
*
* <p>If the requested custom action is not supported by this service, call {@link
* 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);
}
@Override
void onProgressUpdateSent(@Nullable Bundle data) {
receiver.send(RESULT_PROGRESS_UPDATE, data);
}
@Override
void onErrorSent(@Nullable Bundle 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 android.app.ForegroundServiceStartNotAllowedException;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@ -35,7 +34,6 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.ContextCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.PlaybackStateCompat.MediaKeyAction;
import java.util.List;
/**
@ -74,22 +72,6 @@ import java.util.List;
* &lt;/service&gt;
* </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
* foreground.
*
@ -122,7 +104,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
if (Build.VERSION.SDK_INT >= 31
&& Api31.instanceOfForegroundServiceStartNotAllowedException(e)) {
onForegroundServiceStartNotAllowedException(
intent, Api31.castToForegroundServiceStartNotAllowedException(e));
Api31.castToForegroundServiceStartNotAllowedException(e));
} else {
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
* 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.
*/
@RequiresApi(31)
protected void onForegroundServiceStartNotAllowedException(
Intent intent, ForegroundServiceStartNotAllowedException e) {
ForegroundServiceStartNotAllowedException e) {
Log.e(
TAG,
"caught exception when trying to start a foreground service from the "
@ -202,7 +182,6 @@ public class MediaButtonReceiver extends BroadcastReceiver {
mMediaBrowser = mediaBrowser;
}
@SuppressWarnings("deprecation")
@Override
public void onConnected() {
MediaControllerCompat mediaController =
@ -227,112 +206,6 @@ public class MediaButtonReceiver extends BroadcastReceiver {
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

View File

@ -26,7 +26,6 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.service.media.MediaBrowserService;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaBrowserCompat.ConnectionCallback;
@ -36,43 +35,6 @@ import java.util.ArrayList;
@UnstableApi
@RestrictTo(LIBRARY)
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}
@ -106,78 +68,12 @@ public final class MediaConstants {
public static final String SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV =
"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
* {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat} to
* indicate that the corresponding {@link MediaMetadataCompat} or {@link
* MediaBrowserCompat.MediaItem} is an advertisement.
*
* <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()
@ -186,16 +82,6 @@ public final class MediaConstants {
@SuppressLint("IntentName")
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
* 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 =
"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
* 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 =
"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
* 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 =
"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() {}
}

View File

@ -18,7 +18,6 @@ package androidx.media3.session.legacy;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.media.AudioManager;
@ -44,11 +43,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.R;
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
import androidx.media3.session.legacy.PlaybackStateCompat.CustomAction;
import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
@ -124,50 +121,6 @@ public final class MediaControllerCompat {
public static final String 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 */
static void validateCustomAction(@Nullable String action, @Nullable Bundle args) {
if (action == null) {
@ -213,18 +166,13 @@ public final class MediaControllerCompat {
* @param sessionToken The token of the session to be controlled.
*/
public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) {
if (sessionToken == null) {
throw new IllegalArgumentException("sessionToken must not be null");
}
mRegisteredCallbacks = Collections.synchronizedSet(new HashSet<>());
mToken = sessionToken;
if (Build.VERSION.SDK_INT >= 29) {
mImpl = new MediaControllerImplApi29(context, sessionToken);
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, sessionToken);
} else {
mImpl = new MediaControllerImplBase(sessionToken);
mImpl = new MediaControllerImplApi21(context, sessionToken);
}
}
@ -476,17 +424,6 @@ public final class MediaControllerCompat {
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
* does not support {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in {@link
@ -516,16 +453,6 @@ public final class MediaControllerCompat {
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
* handler's thread.
@ -533,11 +460,7 @@ public final class MediaControllerCompat {
* @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.
*/
@SuppressWarnings("deprecation")
public void registerCallback(Callback callback, @Nullable Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
if (!mRegisteredCallbacks.add(callback)) {
Log.w(TAG, "the callback has already been registered");
return;
@ -556,9 +479,6 @@ public final class MediaControllerCompat {
* @param callback The callback to remove
*/
public void unregisterCallback(Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
if (!mRegisteredCallbacks.remove(callback)) {
Log.w(TAG, "the callback has never been registered");
return;
@ -656,12 +576,7 @@ public final class MediaControllerCompat {
// Sharing this in constructor
@SuppressWarnings({"assignment.type.incompatible", "argument.type.incompatible"})
public Callback() {
if (android.os.Build.VERSION.SDK_INT >= 21) {
mCallbackFwk = new MediaControllerCallbackApi21(this);
} else {
mCallbackFwk = null;
mIControllerCallback = new StubCompat(this);
}
mCallbackFwk = new MediaControllerCallback(this);
}
/**
@ -789,11 +704,10 @@ public final class MediaControllerCompat {
}
// Callback methods in this class are run on handler which was given to registerCallback().
@RequiresApi(21)
private static class MediaControllerCallbackApi21 extends MediaController.Callback {
private static class MediaControllerCallback extends MediaController.Callback {
private final WeakReference<MediaControllerCompat.Callback> mCallback;
MediaControllerCallbackApi21(MediaControllerCompat.Callback callback) {
MediaControllerCallback(MediaControllerCompat.Callback callback) {
mCallback = new WeakReference<>(callback);
}
@ -870,7 +784,7 @@ public final class MediaControllerCompat {
callback.onAudioInfoChanged(
new PlaybackInfo(
info.getPlaybackType(),
checkNotNull(AudioAttributesCompat.wrap(info.getAudioAttributes())),
AudioAttributesCompat.wrap(info.getAudioAttributes()),
info.getVolumeControl(),
info.getMaxVolume(),
info.getCurrentVolume()));
@ -886,7 +800,7 @@ public final class MediaControllerCompat {
}
@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();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
@ -894,7 +808,7 @@ public final class MediaControllerCompat {
}
@Override
public void onSessionDestroyed() throws RemoteException {
public void onSessionDestroyed() {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
@ -902,8 +816,7 @@ public final class MediaControllerCompat {
}
@Override
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state)
throws RemoteException {
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
@ -911,7 +824,7 @@ public final class MediaControllerCompat {
}
@Override
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) throws RemoteException {
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
@ -919,7 +832,7 @@ public final class MediaControllerCompat {
}
@Override
public void onQueueChanged(@Nullable List<QueueItem> queue) throws RemoteException {
public void onQueueChanged(@Nullable List<QueueItem> queue) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
@ -927,7 +840,7 @@ public final class MediaControllerCompat {
}
@Override
public void onQueueTitleChanged(@Nullable CharSequence title) throws RemoteException {
public void onQueueTitleChanged(@Nullable CharSequence title) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
@ -935,7 +848,7 @@ public final class MediaControllerCompat {
}
@Override
public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException {
public void onCaptioningEnabledChanged(boolean enabled) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
@ -943,7 +856,7 @@ public final class MediaControllerCompat {
}
@Override
public void onRepeatModeChanged(int repeatMode) throws RemoteException {
public void onRepeatModeChanged(int repeatMode) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
@ -951,12 +864,12 @@ public final class MediaControllerCompat {
}
@Override
public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
public void onShuffleModeChangedRemoved(boolean enabled) {
// Do nothing.
}
@Override
public void onShuffleModeChanged(int shuffleMode) throws RemoteException {
public void onShuffleModeChanged(int shuffleMode) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
@ -964,7 +877,7 @@ public final class MediaControllerCompat {
}
@Override
public void onExtrasChanged(@Nullable Bundle extras) throws RemoteException {
public void onExtrasChanged(@Nullable Bundle extras) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
@ -972,7 +885,7 @@ public final class MediaControllerCompat {
}
@Override
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) throws RemoteException {
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
PlaybackInfo pi = null;
@ -990,7 +903,7 @@ public final class MediaControllerCompat {
}
@Override
public void onSessionReady() throws RemoteException {
public void onSessionReady() {
MediaControllerCompat.Callback callback = mCallback.get();
if (callback != null) {
callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
@ -1231,13 +1144,6 @@ public final class MediaControllerCompat {
*/
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.
*
@ -1448,533 +1354,6 @@ public final class MediaControllerCompat {
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 {
protected final MediaController mControllerFwk;
@ -1983,7 +1362,7 @@ public final class MediaControllerCompat {
@GuardedBy("mLock")
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;
@ -1993,8 +1372,7 @@ public final class MediaControllerCompat {
@SuppressWarnings({"assignment.type.incompatible", "method.invocation.invalid"})
MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) {
mSessionToken = sessionToken;
mControllerFwk =
new MediaController(context, (MediaSession.Token) checkNotNull(mSessionToken.getToken()));
mControllerFwk = new MediaController(context, mSessionToken.getToken());
if (mSessionToken.getExtraBinder() == null) {
requestExtraBinder();
}
@ -2215,7 +1593,7 @@ public final class MediaControllerCompat {
return volumeInfoFwk != null
? new PlaybackInfo(
volumeInfoFwk.getPlaybackType(),
checkNotNull(AudioAttributesCompat.wrap(volumeInfoFwk.getAudioAttributes())),
AudioAttributesCompat.wrap(volumeInfoFwk.getAudioAttributes()),
volumeInfoFwk.getVolumeControl(),
volumeInfoFwk.getMaxVolume(),
volumeInfoFwk.getCurrentVolume())
@ -2304,29 +1682,8 @@ public final class MediaControllerCompat {
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 WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
private final WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl) {
super(null /* handler */);
@ -2357,37 +1714,37 @@ public final class MediaControllerCompat {
}
@Override
public void onSessionDestroyed() throws RemoteException {
public void onSessionDestroyed() {
// Will not be called.
throw new AssertionError();
}
@Override
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) throws RemoteException {
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) {
// Will not be called.
throw new AssertionError();
}
@Override
public void onQueueChanged(@Nullable List<QueueItem> queue) throws RemoteException {
public void onQueueChanged(@Nullable List<QueueItem> queue) {
// Will not be called.
throw new AssertionError();
}
@Override
public void onQueueTitleChanged(@Nullable CharSequence title) throws RemoteException {
public void onQueueTitleChanged(@Nullable CharSequence title) {
// Will not be called.
throw new AssertionError();
}
@Override
public void onExtrasChanged(@Nullable Bundle extras) throws RemoteException {
public void onExtrasChanged(@Nullable Bundle extras) {
// Will not be called.
throw new AssertionError();
}
@Override
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) throws RemoteException {
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info) {
// Will not be called.
throw new AssertionError();
}
@ -2411,7 +1768,6 @@ public final class MediaControllerCompat {
}
}
@RequiresApi(21)
static class TransportControlsApi21 extends TransportControls {
protected final MediaController.TransportControls mControlsFwk;
@ -2491,7 +1847,7 @@ public final class MediaControllerCompat {
@SuppressWarnings("argument.type.incompatible") // Platform controller accepts null rating
@Override
public void setRating(RatingCompat rating) {
mControlsFwk.setRating(rating != null ? (Rating) rating.getRating() : null);
mControlsFwk.setRating((Rating) rating.getRating());
}
@Override
@ -2514,13 +1870,6 @@ public final class MediaControllerCompat {
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
public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
Bundle bundle = new Bundle();
@ -2549,7 +1898,7 @@ public final class MediaControllerCompat {
@Override
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.");
}
Bundle bundle = new Bundle();

View File

@ -16,7 +16,6 @@
package androidx.media3.session.legacy;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
@ -26,7 +25,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
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
* Builder or retrieved from existing metadata using {@link MediaMetadataCompat#getDescription()}.
* Builder.
*/
@UnstableApi
@RestrictTo(LIBRARY)
@SuppressLint("BanParcelableUsage")
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
@ -60,7 +57,8 @@ public final class MediaDescriptionCompat implements Parcelable {
*
* @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
@ -196,20 +194,6 @@ public final class MediaDescriptionCompat implements Parcelable {
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}. */
@Nullable
public String getMediaId() {
@ -293,18 +277,7 @@ public final class MediaDescriptionCompat implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
if (Build.VERSION.SDK_INT < 21) {
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);
}
getMediaDescription().writeToParcel(dest, flags);
}
@Override
@ -315,22 +288,19 @@ public final class MediaDescriptionCompat implements Parcelable {
/**
* 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, or null if none.
* @return An equivalent {@link android.media.MediaDescription} object.
*/
@RequiresApi(21)
public Object getMediaDescription() {
public MediaDescription getMediaDescription() {
if (mDescriptionFwk != null) {
return mDescriptionFwk;
}
MediaDescription.Builder bob = Api21Impl.createBuilder();
Api21Impl.setMediaId(bob, mMediaId);
Api21Impl.setTitle(bob, mTitle);
Api21Impl.setSubtitle(bob, mSubtitle);
Api21Impl.setDescription(bob, mDescription);
Api21Impl.setIconBitmap(bob, mIcon);
Api21Impl.setIconUri(bob, mIconUri);
MediaDescription.Builder bob = new MediaDescription.Builder();
bob.setMediaId(mMediaId);
bob.setTitle(mTitle);
bob.setSubtitle(mSubtitle);
bob.setDescription(mDescription);
bob.setIconBitmap(mIcon);
bob.setIconUri(mIconUri);
// 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
// fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns
@ -344,14 +314,14 @@ public final class MediaDescriptionCompat implements Parcelable {
extras = new Bundle(mExtras);
}
extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
Api21Impl.setExtras(bob, extras);
bob.setExtras(extras);
} else {
Api21Impl.setExtras(bob, mExtras);
bob.setExtras(mExtras);
}
if (Build.VERSION.SDK_INT >= 23) {
Api23Impl.setMediaUri(bob, mMediaUri);
}
mDescriptionFwk = Api21Impl.build(bob);
mDescriptionFwk = bob.build();
return mDescriptionFwk;
}
@ -359,71 +329,56 @@ public final class MediaDescriptionCompat implements Parcelable {
/**
* Creates an instance from a framework {@link android.media.MediaDescription} object.
*
* <p>This method is only supported on API 21+.
*
* @param descriptionObj A {@link android.media.MediaDescription} object, or null if none.
* @return An equivalent {@link MediaMetadataCompat} object, or null if none.
* @param description A {@link android.media.MediaDescription} object.
* @return An equivalent {@link MediaMetadataCompat} object.
*/
@Nullable
@SuppressWarnings("deprecation")
public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
if (descriptionObj != null && Build.VERSION.SDK_INT >= 21) {
Builder bob = new Builder();
MediaDescription description = (MediaDescription) descriptionObj;
bob.setMediaId(Api21Impl.getMediaId(description));
bob.setTitle(Api21Impl.getTitle(description));
bob.setSubtitle(Api21Impl.getSubtitle(description));
bob.setDescription(Api21Impl.getDescription(description));
bob.setIconBitmap(Api21Impl.getIconBitmap(description));
bob.setIconUri(Api21Impl.getIconUri(description));
Bundle extras = Api21Impl.getExtras(description);
extras = MediaSessionCompat.unparcelWithClassLoader(extras);
if (extras != null) {
extras = new Bundle(extras);
}
Uri mediaUri = null;
if (extras != null) {
mediaUri = extras.getParcelable(DESCRIPTION_KEY_MEDIA_URI);
if (mediaUri != null) {
if (extras.containsKey(DESCRIPTION_KEY_NULL_BUNDLE_FLAG) && extras.size() == 2) {
// The extras were only created for the media URI, so we set it back to null to
// ensure mediaDescriptionCompat.getExtras() equals
// fromMediaDescription(getMediaDescription(mediaDescriptionCompat)).getExtras()
extras = null;
} else {
// Remove media URI keys to ensure mediaDescriptionCompat.getExtras().keySet()
// equals fromMediaDescription(getMediaDescription(mediaDescriptionCompat))
// .getExtras().keySet()
extras.remove(DESCRIPTION_KEY_MEDIA_URI);
extras.remove(DESCRIPTION_KEY_NULL_BUNDLE_FLAG);
}
public static MediaDescriptionCompat fromMediaDescription(MediaDescription description) {
Builder bob = new Builder();
bob.setMediaId(description.getMediaId());
bob.setTitle(description.getTitle());
bob.setSubtitle(description.getSubtitle());
bob.setDescription(description.getDescription());
bob.setIconBitmap(description.getIconBitmap());
bob.setIconUri(description.getIconUri());
Bundle extras = description.getExtras();
extras = MediaSessionCompat.unparcelWithClassLoader(extras);
if (extras != null) {
extras = new Bundle(extras);
}
Uri mediaUri = null;
if (extras != null) {
mediaUri = extras.getParcelable(DESCRIPTION_KEY_MEDIA_URI);
if (mediaUri != null) {
if (extras.containsKey(DESCRIPTION_KEY_NULL_BUNDLE_FLAG) && extras.size() == 2) {
// The extras were only created for the media URI, so we set it back to null to
// ensure mediaDescriptionCompat.getExtras() equals
// fromMediaDescription(getMediaDescription(mediaDescriptionCompat)).getExtras()
extras = null;
} else {
// Remove media URI keys to ensure mediaDescriptionCompat.getExtras().keySet()
// equals fromMediaDescription(getMediaDescription(mediaDescriptionCompat))
// .getExtras().keySet()
extras.remove(DESCRIPTION_KEY_MEDIA_URI);
extras.remove(DESCRIPTION_KEY_NULL_BUNDLE_FLAG);
}
}
bob.setExtras(extras);
if (mediaUri != null) {
bob.setMediaUri(mediaUri);
} else if (Build.VERSION.SDK_INT >= 23) {
bob.setMediaUri(Api23Impl.getMediaUri(description));
}
MediaDescriptionCompat descriptionCompat = bob.build();
descriptionCompat.mDescriptionFwk = description;
return descriptionCompat;
} else {
return null;
}
bob.setExtras(extras);
if (mediaUri != null) {
bob.setMediaUri(mediaUri);
} else if (Build.VERSION.SDK_INT >= 23) {
bob.setMediaUri(Api23Impl.getMediaUri(description));
}
MediaDescriptionCompat descriptionCompat = bob.build();
descriptionCompat.mDescriptionFwk = description;
return descriptionCompat;
}
public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR =
new Parcelable.Creator<MediaDescriptionCompat>() {
@Override
public MediaDescriptionCompat createFromParcel(Parcel in) {
if (Build.VERSION.SDK_INT < 21) {
return new MediaDescriptionCompat(in);
} else {
return checkNotNull(
fromMediaDescription(MediaDescription.CREATOR.createFromParcel(in)));
}
return fromMediaDescription(MediaDescription.CREATOR.createFromParcel(in));
}
@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)
private static class Api23Impl {
private Api23Impl() {}

View File

@ -22,23 +22,18 @@ import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
import androidx.collection.ArrayMap;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaControllerCompat.TransportControls;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
/** Contains metadata about an item, such as the title, artist, etc. */
@UnstableApi
@ -48,55 +43,55 @@ public final class MediaMetadataCompat implements Parcelable {
private static final String TAG = "MediaMetadata";
/** 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. */
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
* 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. */
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. */
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. */
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. */
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. */
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
* 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. */
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. */
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. */
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. */
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. */
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. */
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}.
@ -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
* 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. */
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
* 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.
*/
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. */
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.
*
* @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.
*
* @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
* #METADATA_KEY_TITLE} but may differ for some formats. When displaying media described by this
* 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
* described by this metadata this should be preferred to other fields if present.
*/
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
* media described by this metadata this should be preferred to other fields if present.
*/
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
@ -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
* 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
@ -170,20 +165,21 @@ public final class MediaMetadataCompat implements Parcelable {
* fields when present. This must be a Uri style String.
*/
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
* 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
* 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.
*/
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
@ -199,7 +195,9 @@ public final class MediaMetadataCompat implements Parcelable {
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_YEARS}
* </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
@ -276,7 +274,7 @@ public final class MediaMetadataCompat implements Parcelable {
static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
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_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
@ -320,17 +318,8 @@ public final class MediaMetadataCompat implements Parcelable {
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;
@Nullable private MediaMetadata mMetadataFwk;
@Nullable private MediaDescriptionCompat mDescription;
MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle);
@ -396,7 +385,6 @@ public final class MediaMetadataCompat implements Parcelable {
* @return A {@link RatingCompat} or null
*/
@Nullable
@SuppressWarnings("deprecation")
public RatingCompat getRating(@RatingKey String key) {
RatingCompat rating = null;
try {
@ -417,7 +405,6 @@ public final class MediaMetadataCompat implements Parcelable {
* @return A {@link Bitmap} or null
*/
@Nullable
@SuppressWarnings("deprecation")
public Bitmap getBitmap(@BitmapKey String key) {
Bitmap bmp = null;
try {
@ -429,93 +416,6 @@ public final class MediaMetadataCompat implements Parcelable {
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
public int describeContents() {
return 0;
@ -535,15 +435,6 @@ public final class MediaMetadataCompat implements Parcelable {
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
* compatibility.
@ -564,7 +455,7 @@ public final class MediaMetadataCompat implements Parcelable {
*/
@Nullable
public static MediaMetadataCompat fromMediaMetadata(@Nullable Object metadataObj) {
if (metadataObj != null && Build.VERSION.SDK_INT >= 21) {
if (metadataObj != null) {
Parcel p = Parcel.obtain();
((MediaMetadata) metadataObj).writeToParcel(p, 0);
p.setDataPosition(0);
@ -580,12 +471,9 @@ public final class MediaMetadataCompat implements Parcelable {
/**
* 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, or null if none.
* @return An equivalent {@link android.media.MediaMetadata} object.
*/
@RequiresApi(21)
public Object getMediaMetadata() {
public MediaMetadata getMediaMetadata() {
if (mMetadataFwk == null) {
Parcel p = Parcel.obtain();
try {
@ -628,39 +516,6 @@ public final class MediaMetadataCompat implements Parcelable {
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
* 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() {
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.
*/
public static MediaSessionManager getSessionManager(Context context) {
if (context == null) {
throw new IllegalArgumentException("context cannot be null");
}
synchronized (sLock) {
if (sSessionManager == null) {
sSessionManager = new MediaSessionManager(context.getApplicationContext());
@ -69,13 +66,7 @@ public final class MediaSessionManager {
}
private MediaSessionManager(Context context) {
if (Build.VERSION.SDK_INT >= 28) {
mImpl = new MediaSessionManagerImplApi28(context);
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaSessionManagerImplApi21(context);
} else {
mImpl = new MediaSessionManagerImplBase(context);
}
mImpl = new MediaSessionManagerImpl(context);
}
/**
@ -91,22 +82,9 @@ public final class MediaSessionManager {
* {@code false} otherwise.
*/
public boolean isTrustedForMediaControl(RemoteUserInfo userInfo) {
if (userInfo == null) {
throw new IllegalArgumentException("userInfo should not be null");
}
return mImpl.isTrustedForMediaControl(userInfo.mImpl);
}
Context getContext() {
return mImpl.getContext();
}
interface MediaSessionManagerImpl {
Context getContext();
boolean isTrustedForMediaControl(RemoteUserInfoImpl userInfo);
}
interface RemoteUserInfoImpl {
String getPackageName();
@ -157,10 +135,10 @@ public final class MediaSessionManager {
throw new IllegalArgumentException("packageName should be nonempty");
}
if (Build.VERSION.SDK_INT >= 28) {
mImpl = new MediaSessionManagerImplApi28.RemoteUserInfoImplApi28(packageName, pid, uid);
mImpl = new RemoteUserInfoImplApi28(packageName, pid, uid);
} else {
// 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) {
// Framework RemoteUserInfo doesn't ensure non-null nor non-empty package name,
// so ensure package name here instead.
String packageName =
MediaSessionManagerImplApi28.RemoteUserInfoImplApi28.getPackageName(remoteUserInfo);
String packageName = RemoteUserInfoImplApi28.getPackageName(remoteUserInfo);
if (packageName == null) {
throw new NullPointerException("package shouldn't be null");
} else if (TextUtils.isEmpty(packageName)) {
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
implements MediaSessionManager.MediaSessionManagerImpl {
private static class MediaSessionManagerImpl {
private static final String TAG = MediaSessionManager.TAG;
private static final boolean DEBUG = MediaSessionManager.DEBUG;
@ -255,19 +231,24 @@ public final class MediaSessionManager {
Context mContext;
ContentResolver mContentResolver;
MediaSessionManagerImplBase(Context context) {
MediaSessionManagerImpl(Context context) {
mContext = context;
mContentResolver = mContext.getContentResolver();
}
@Override
public Context getContext() {
return mContext;
}
@Override
@SuppressWarnings("deprecation")
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 {
ApplicationInfo applicationInfo =
mContext.getPackageManager().getApplicationInfo(userInfo.getPackageName(), 0);
@ -286,6 +267,15 @@ public final class MediaSessionManager {
|| 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(
MediaSessionManager.RemoteUserInfoImpl userInfo, String permission) {
if (userInfo.getPid() < 0) {
@ -320,142 +310,88 @@ public final class MediaSessionManager {
}
return false;
}
}
static class RemoteUserInfoImplBase implements MediaSessionManager.RemoteUserInfoImpl {
private String mPackageName;
private int mPid;
private int mUid;
private static class RemoteUserInfoImplBase implements MediaSessionManager.RemoteUserInfoImpl {
private final String mPackageName;
private final int mPid;
private final int mUid;
RemoteUserInfoImplBase(String packageName, int pid, int uid) {
mPackageName = packageName;
mPid = pid;
mUid = uid;
RemoteUserInfoImplBase(String packageName, int pid, int uid) {
mPackageName = packageName;
mPid = pid;
mUid = uid;
}
@Override
public String getPackageName() {
return mPackageName;
}
@Override
public int getPid() {
return mPid;
}
@Override
public int getUid() {
return mUid;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@Override
public String getPackageName() {
return mPackageName;
if (!(obj instanceof RemoteUserInfoImplBase)) {
return false;
}
@Override
public int getPid() {
return mPid;
}
@Override
public int getUid() {
return mUid;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof RemoteUserInfoImplBase)) {
return false;
}
RemoteUserInfoImplBase otherUserInfo = (RemoteUserInfoImplBase) obj;
if (mPid < 0 || otherUserInfo.mPid < 0) {
// Only compare package name and UID when PID is unknown.
return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
&& mUid == otherUserInfo.mUid;
}
RemoteUserInfoImplBase otherUserInfo = (RemoteUserInfoImplBase) obj;
if (mPid < 0 || otherUserInfo.mPid < 0) {
// Only compare package name and UID when PID is unknown.
return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
&& mPid == otherUserInfo.mPid
&& mUid == otherUserInfo.mUid;
}
@Override
public int hashCode() {
return ObjectsCompat.hash(mPackageName, mUid);
}
}
}
@RequiresApi(21)
private static class MediaSessionManagerImplApi21 extends MediaSessionManagerImplBase {
MediaSessionManagerImplApi21(Context context) {
super(context);
mContext = context;
return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
&& mPid == otherUserInfo.mPid
&& mUid == otherUserInfo.mUid;
}
@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;
public int hashCode() {
return ObjectsCompat.hash(mPackageName, mUid);
}
}
/**
* This extends {@link RemoteUserInfoImplBase} on purpose not to use frameworks' equals() and
* hashCode() implementation for two reasons:
*
* <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
* {@link android.media.session.MediaSessionManager.RemoteUserInfo} also checks internal binder to
* distinguish multiple {@link android.media.session.MediaController} and {@link
* android.media.browse.MediaBrowser} in a process. However, when the binders in both
* RemoteUserInfos are {@code null}, framework's equal() specially handles the case and returns
* {@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
* public constructors are considered as all different.
*/
@RequiresApi(28)
private static final class MediaSessionManagerImplApi28 extends MediaSessionManagerImplApi21 {
@Nullable android.media.session.MediaSessionManager mObject;
private static final class RemoteUserInfoImplApi28 extends RemoteUserInfoImplBase {
MediaSessionManagerImplApi28(Context context) {
super(context);
mObject =
(android.media.session.MediaSessionManager)
context.getSystemService(Context.MEDIA_SESSION_SERVICE);
RemoteUserInfoImplApi28(String packageName, int pid, int uid) {
super(packageName, pid, uid);
}
@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);
RemoteUserInfoImplApi28(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
super(remoteUserInfo.getPackageName(), remoteUserInfo.getPid(), remoteUserInfo.getUid());
}
/**
* This extends {@link RemoteUserInfoImplBase} on purpose not to use frameworks' equals() and
* hashCode() implementation for two reasons:
*
* <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
* {@link android.media.session.MediaSessionManager.RemoteUserInfo} also checks internal binder
* to distinguish multiple {@link android.media.session.MediaController} and {@link
* 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
* {@code false}. This cause two issues that we need to workaround. Issue a) RemoteUserInfos
* created by key events are considered as all different. issue b) RemoteUserInfos created with
* public constructors are considers as all different.
*/
@RequiresApi(28)
private static final class RemoteUserInfoImplApi28 extends RemoteUserInfoImplBase {
final android.media.session.MediaSessionManager.RemoteUserInfo mObject;
RemoteUserInfoImplApi28(String packageName, int pid, int uid) {
super(packageName, pid, uid);
mObject =
new android.media.session.MediaSessionManager.RemoteUserInfo(packageName, pid, uid);
}
RemoteUserInfoImplApi28(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
super(remoteUserInfo.getPackageName(), remoteUserInfo.getPid(), remoteUserInfo.getUid());
mObject = remoteUserInfo;
}
static String getPackageName(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
return remoteUserInfo.getPackageName();
}
static String getPackageName(
android.media.session.MediaSessionManager.RemoteUserInfo remoteUserInfo) {
return remoteUserInfo.getPackageName();
}
}
}

View File

@ -37,15 +37,6 @@ public class ParcelableVolumeInfo implements Parcelable {
public int maxVolume;
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) {
volumeType = from.readInt();
controlType = from.readInt();

View File

@ -26,7 +26,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.KeyEvent;
import androidx.annotation.IntDef;
import androidx.annotation.LongDef;
import androidx.annotation.Nullable;
@ -532,49 +531,6 @@ public final class PlaybackStateCompat implements Parcelable {
*/
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 long mPosition;
final long mBufferedPosition;
@ -712,7 +668,7 @@ public final class PlaybackStateCompat implements Parcelable {
* @param timeDiff Only used for testing, otherwise it should be null.
* @return The current playback position in ms
*/
public long getCurrentPosition(Long timeDiff) {
public long getCurrentPosition(@Nullable Long timeDiff) {
long expectedPosition =
mPosition
+ (long)
@ -776,7 +732,6 @@ public final class PlaybackStateCompat implements Parcelable {
}
/** Get the list of custom actions. */
@Nullable
public List<PlaybackStateCompat.CustomAction> getCustomActions() {
return mCustomActions;
}
@ -839,20 +794,17 @@ public final class PlaybackStateCompat implements Parcelable {
/**
* Creates an instance from a framework {@link android.media.session.PlaybackState} object.
*
* <p>This method is only supported on API 21+.
*
* @param stateObj A {@link android.media.session.PlaybackState} object, or null if none.
* @param stateFwk A {@link android.media.session.PlaybackState} object, or null if none.
* @return An equivalent {@link PlaybackStateCompat} object, or null if none.
*/
@Nullable
public static PlaybackStateCompat fromPlaybackState(@Nullable Object stateObj) {
if (stateObj != null && Build.VERSION.SDK_INT >= 21) {
PlaybackState stateFwk = (PlaybackState) stateObj;
List<PlaybackState.CustomAction> customActionFwks = Api21Impl.getCustomActions(stateFwk);
public static PlaybackStateCompat fromPlaybackState(@Nullable PlaybackState stateFwk) {
if (stateFwk != null) {
List<PlaybackState.CustomAction> customActionFwks = stateFwk.getCustomActions();
List<PlaybackStateCompat.CustomAction> customActions = null;
if (customActionFwks != null) {
customActions = new ArrayList<>(customActionFwks.size());
for (Object customActionFwk : customActionFwks) {
for (PlaybackState.CustomAction customActionFwk : customActionFwks) {
if (customActionFwk == null) {
continue;
}
@ -868,16 +820,16 @@ public final class PlaybackStateCompat implements Parcelable {
}
PlaybackStateCompat stateCompat =
new PlaybackStateCompat(
Api21Impl.getState(stateFwk),
Api21Impl.getPosition(stateFwk),
Api21Impl.getBufferedPosition(stateFwk),
Api21Impl.getPlaybackSpeed(stateFwk),
Api21Impl.getActions(stateFwk),
stateFwk.getState(),
stateFwk.getPosition(),
stateFwk.getBufferedPosition(),
stateFwk.getPlaybackSpeed(),
stateFwk.getActions(),
ERROR_CODE_UNKNOWN_ERROR,
Api21Impl.getErrorMessage(stateFwk),
Api21Impl.getLastPositionUpdateTime(stateFwk),
stateFwk.getErrorMessage(),
stateFwk.getLastPositionUpdateTime(),
customActions,
Api21Impl.getActiveQueueItemId(stateFwk),
stateFwk.getActiveQueueItemId(),
extras);
stateCompat.mStateFwk = stateFwk;
return stateCompat;
@ -889,30 +841,28 @@ public final class PlaybackStateCompat implements Parcelable {
/**
* 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, or null if none.
* @return An equivalent {@link android.media.session.PlaybackState} object.
*/
@Nullable
public Object getPlaybackState() {
if (mStateFwk == null && Build.VERSION.SDK_INT >= 21) {
PlaybackState.Builder builder = Api21Impl.createBuilder();
Api21Impl.setState(builder, mState, mPosition, mSpeed, mUpdateTime);
Api21Impl.setBufferedPosition(builder, mBufferedPosition);
Api21Impl.setActions(builder, mActions);
Api21Impl.setErrorMessage(builder, mErrorMessage);
@SuppressWarnings("argument.type.incompatible") // setErrorMessage not annotated as nullable
public PlaybackState getPlaybackState() {
if (mStateFwk == null) {
PlaybackState.Builder builder = new PlaybackState.Builder();
builder.setState(mState, mPosition, mSpeed, mUpdateTime);
builder.setBufferedPosition(mBufferedPosition);
builder.setActions(mActions);
builder.setErrorMessage(mErrorMessage);
for (PlaybackStateCompat.CustomAction customAction : mCustomActions) {
PlaybackState.CustomAction action =
(PlaybackState.CustomAction) customAction.getCustomAction();
if (action != null) {
Api21Impl.addCustomAction(builder, action);
builder.addCustomAction(action);
}
}
Api21Impl.setActiveQueueItemId(builder, mActiveItemId);
builder.setActiveQueueItemId(mActiveItemId);
if (Build.VERSION.SDK_INT >= 22) {
Api22Impl.setExtras(builder, mExtras);
}
mStateFwk = Api21Impl.build(builder);
mStateFwk = builder.build();
}
return mStateFwk;
}
@ -975,22 +925,19 @@ public final class PlaybackStateCompat implements Parcelable {
* Creates an instance from a framework {@link android.media.session.PlaybackState.CustomAction}
* object.
*
* <p>This method is only supported on API 21+.
*
* @param customActionObj A {@link android.media.session.PlaybackState.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) {
PlaybackState.CustomAction customActionFwk = (PlaybackState.CustomAction) customActionObj;
Bundle extras = Api21Impl.getExtras(customActionFwk);
Bundle extras = customActionFwk.getExtras();
MediaSessionCompat.ensureClassLoader(extras);
PlaybackStateCompat.CustomAction customActionCompat =
new PlaybackStateCompat.CustomAction(
Api21Impl.getAction(customActionFwk),
Api21Impl.getName(customActionFwk),
Api21Impl.getIcon(customActionFwk),
customActionFwk.getAction(),
customActionFwk.getName(),
customActionFwk.getIcon(),
extras);
customActionCompat.mCustomActionFwk = customActionFwk;
return customActionCompat;
@ -1000,21 +947,20 @@ public final class PlaybackStateCompat implements Parcelable {
* Gets the underlying framework {@link android.media.session.PlaybackState.CustomAction}
* object.
*
* <p>This method is only supported on API 21+.
*
* @return An equivalent {@link android.media.session.PlaybackState.CustomAction} object, or
* null if none.
*/
@SuppressWarnings("argument.type.incompatible") // setExtras not annotated as nullable
@Nullable
public Object getCustomAction() {
if (mCustomActionFwk != null || Build.VERSION.SDK_INT < 21) {
if (mCustomActionFwk != null) {
return mCustomActionFwk;
}
PlaybackState.CustomAction.Builder builder =
Api21Impl.createCustomActionBuilder(mAction, mName, mIcon);
Api21Impl.setExtras(builder, mExtras);
return Api21Impl.build(builder);
new PlaybackState.CustomAction.Builder(mAction, mName, mIcon);
builder.setExtras(mExtras);
return builder.build();
}
public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR =
@ -1325,10 +1271,6 @@ public final class PlaybackStateCompat implements Parcelable {
* @return this
*/
public Builder addCustomAction(PlaybackStateCompat.CustomAction customAction) {
if (customAction == null) {
throw new IllegalArgumentException(
"You may not add a null CustomAction to PlaybackStateCompat");
}
mCustomActions.add(customAction);
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)
private static class Api22Impl {
private Api22Impl() {}

View File

@ -21,7 +21,6 @@ import android.media.VolumeProvider;
import android.os.Build;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Retention;
@ -96,25 +95,6 @@ public abstract class VolumeProviderCompat {
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.
*
@ -131,26 +111,13 @@ public abstract class VolumeProviderCompat {
*/
public final void setCurrentVolume(int currentVolume) {
mCurrentVolume = currentVolume;
if (Build.VERSION.SDK_INT >= 21) {
VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider();
Api21Impl.setCurrentVolume(volumeProviderFwk, currentVolume);
}
VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider();
volumeProviderFwk.setCurrentVolume(currentVolume);
if (mCallback != null) {
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.
*
@ -181,7 +148,6 @@ public abstract class VolumeProviderCompat {
*
* @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
*/
@RequiresApi(21)
public Object getVolumeProvider() {
if (mVolumeProviderFwk == null) {
if (Build.VERSION.SDK_INT >= 30) {
@ -219,13 +185,4 @@ public abstract class VolumeProviderCompat {
public abstract static class Callback {
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();
}
@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
public void convertToQueueItemId() {
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.AudioManager;
import android.os.Build;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -32,55 +29,45 @@ import org.junit.runner.RunWith;
/** Test {@link AudioAttributesCompat}. */
@RunWith(AndroidJUnit4.class)
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) {
return new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream);
}
// some objects we'll toss around
Object mMediaAA;
AudioAttributesCompat mMediaAAC;
AudioAttributesCompat mMediaLegacyAAC;
AudioAttributesCompat mMediaAACFromAA;
AudioAttributesCompat mNotificationAAC;
AudioAttributesCompat mNotificationLegacyAAC;
private Object mMediaAA;
private AudioAttributesCompat mMediaAAC;
private AudioAttributesCompat mMediaLegacyAAC;
private AudioAttributesCompat mMediaAACFromAA;
private AudioAttributesCompat mNotificationAAC;
private AudioAttributesCompat mNotificationLegacyAAC;
@Before
@SdkSuppress(minSdkVersion = 21)
public void setUpApi21() {
if (Build.VERSION.SDK_INT < 21) {
return;
}
mMediaAA =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build();
mMediaAACFromAA = AudioAttributesCompat.wrap((AudioAttributes) mMediaAA);
mMediaAACFromAA = AudioAttributesCompat.wrap(mMediaAA);
}
@Before
public void setUp() {
mMediaAAC =
mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC, AudioAttributesCompat.USAGE_MEDIA)
new AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
.build();
mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
mMediaLegacyAAC =
new AudioAttributesCompat.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
mNotificationAAC =
mkBuilder(
AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
AudioAttributesCompat.USAGE_NOTIFICATION)
new AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributesCompat.USAGE_NOTIFICATION)
.build();
mNotificationLegacyAAC =
new AudioAttributesCompat.Builder()
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build();
mNotificationLegacyAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void testCreateWithAudioAttributesApi21() {
assertThat(mMediaAACFromAA, not(equalTo(null)));
assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA));
@ -90,7 +77,6 @@ public class AudioAttributesCompatTest {
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void testEqualityApi21() {
assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
@ -126,59 +112,35 @@ public class AudioAttributesCompatTest {
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void testLegacyStreamTypeInferenceApi21() {
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
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.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.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.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.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 androidx.media3.test.session.common.TestUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -30,7 +29,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class MediaDescriptionCompatTest {
@SdkSuppress(minSdkVersion = 21)
@Test
public void roundTripViaFrameworkObject_returnsEqualMediaUriAndExtras() {
Uri mediaUri = Uri.parse("androidx://media/uri");
@ -54,7 +52,6 @@ public class MediaDescriptionCompatTest {
TestUtils.equals(createExtras(), restoredDescription2.getExtras());
}
@SdkSuppress(minSdkVersion = 21)
@Test
public void getMediaDescription_withMediaUri_doesNotTouchExtras() {
MediaDescriptionCompat originalDescription =