Import androidx.media
This imports all the classes and resources needed by our code. We still have the nominal dependency on the artifact as we need to access the Parcelable CREATORs of MediaSessionCompat.Token, MediaDescriptionCompat, RatingCompat and MediaBrowserCompat.MediaItem. Mechanical import steps: - Put all files under a new 'legacy' package and change all import statements accordingly. - Reformat to adhere to Media3 java style guide - Remove all existing @RestrictTo annotations and replace them with top-level @RestrictTo(LIBRARY) on all classes in the new package. - Remove @NonNull annotations and fixed nullability test errors - Fix HTML javadoc build errors - Fix Lint errors (but not warnings) The code still contains many lint warnings that will be fixed separately. PiperOrigin-RevId: 627999285
This commit is contained in:
parent
43d1fa933c
commit
d0d6ce52a5
@ -37,7 +37,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
*
|
||||
* <p>The generic {@code T} denotes a key of connected {@link MediaController controllers}, and it
|
||||
* can be either {@link android.os.IBinder} or {@link
|
||||
* androidx.media.MediaSessionManager.RemoteUserInfo}.
|
||||
* androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo}.
|
||||
*
|
||||
* <p>This class is thread-safe.
|
||||
*/
|
||||
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
|
||||
import static androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
|
||||
import static androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS;
|
||||
import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS;
|
||||
import static androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES;
|
||||
@ -44,6 +42,8 @@ import static androidx.media3.common.Player.COMMAND_STOP;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Util.constrainValue;
|
||||
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
||||
import static androidx.media3.session.legacy.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
|
||||
import static androidx.media3.session.legacy.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@ -54,19 +54,8 @@ import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.RatingCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.AudioAttributesCompat;
|
||||
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
|
||||
import androidx.media.VolumeProviderCompat;
|
||||
import androidx.media3.common.AdPlaybackState;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
@ -87,6 +76,17 @@ import androidx.media3.common.Timeline.Window;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.legacy.AudioAttributesCompat;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat.BrowserRoot;
|
||||
import androidx.media3.session.legacy.MediaControllerCompat;
|
||||
import androidx.media3.session.legacy.MediaDescriptionCompat;
|
||||
import androidx.media3.session.legacy.MediaMetadataCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat.CustomAction;
|
||||
import androidx.media3.session.legacy.RatingCompat;
|
||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -1231,12 +1231,15 @@ import java.util.concurrent.TimeoutException;
|
||||
sessionCommandsBuilder.remove(SessionCommand.COMMAND_CODE_SESSION_SET_RATING);
|
||||
}
|
||||
|
||||
if (state != null && state.getCustomActions() != null) {
|
||||
for (CustomAction customAction : state.getCustomActions()) {
|
||||
String action = customAction.getAction();
|
||||
@Nullable Bundle extras = customAction.getExtras();
|
||||
sessionCommandsBuilder.add(
|
||||
new SessionCommand(action, extras == null ? Bundle.EMPTY : extras));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sessionCommandsBuilder.build();
|
||||
@ -1254,8 +1257,12 @@ import java.util.concurrent.TimeoutException;
|
||||
if (state == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
List<PlaybackStateCompat.CustomAction> customActions = state.getCustomActions();
|
||||
if (customActions == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
ImmutableList.Builder<CommandButton> layout = new ImmutableList.Builder<>();
|
||||
for (CustomAction customAction : state.getCustomActions()) {
|
||||
for (CustomAction customAction : customActions) {
|
||||
String action = customAction.getAction();
|
||||
@Nullable Bundle extras = customAction.getExtras();
|
||||
@CommandButton.Icon
|
||||
|
@ -23,9 +23,6 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaBrowserCompat.ItemCallback;
|
||||
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -33,6 +30,9 @@ import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat.ItemCallback;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat.SubscriptionCallback;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@ -224,7 +224,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
new MediaBrowserCompat.SearchCallback() {
|
||||
@Override
|
||||
public void onSearchResult(
|
||||
String query, Bundle extras, List<MediaBrowserCompat.MediaItem> items) {
|
||||
String query, @Nullable Bundle extras, List<MediaBrowserCompat.MediaItem> items) {
|
||||
getInstance()
|
||||
.notifyBrowserListener(
|
||||
listener -> {
|
||||
@ -238,7 +238,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String query, Bundle extras) {
|
||||
public void onError(String query, @Nullable Bundle extras) {
|
||||
getInstance()
|
||||
.notifyBrowserListener(
|
||||
listener -> {
|
||||
@ -276,7 +276,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
new MediaBrowserCompat.SearchCallback() {
|
||||
@Override
|
||||
public void onSearchResult(
|
||||
String query, Bundle extrasSent, List<MediaBrowserCompat.MediaItem> items) {
|
||||
String query, @Nullable Bundle extrasSent, List<MediaBrowserCompat.MediaItem> items) {
|
||||
future.set(
|
||||
LibraryResult.ofItemList(
|
||||
LegacyConversions.convertBrowserItemListToMediaItemList(items),
|
||||
@ -284,7 +284,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String query, Bundle extrasSent) {
|
||||
public void onError(String query, @Nullable Bundle extrasSent) {
|
||||
future.set(LibraryResult.ofError(RESULT_ERROR_UNKNOWN));
|
||||
}
|
||||
});
|
||||
@ -370,23 +370,26 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String parentId) {
|
||||
public void onError(@Nullable String parentId) {
|
||||
onErrorInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String parentId, Bundle options) {
|
||||
public void onError(@Nullable String parentId, @Nullable Bundle options) {
|
||||
onErrorInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children) {
|
||||
public void onChildrenLoaded(
|
||||
@Nullable String parentId, @Nullable List<MediaBrowserCompat.MediaItem> children) {
|
||||
onChildrenLoadedInternal(parentId, children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildrenLoaded(
|
||||
String parentId, List<MediaBrowserCompat.MediaItem> children, Bundle options) {
|
||||
@Nullable String parentId,
|
||||
@Nullable List<MediaBrowserCompat.MediaItem> children,
|
||||
@Nullable Bundle options) {
|
||||
onChildrenLoadedInternal(parentId, children);
|
||||
}
|
||||
|
||||
@ -397,7 +400,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
private void onChildrenLoadedInternal(
|
||||
String parentId, @Nullable List<MediaBrowserCompat.MediaItem> children) {
|
||||
@Nullable String parentId, @Nullable List<MediaBrowserCompat.MediaItem> children) {
|
||||
if (TextUtils.isEmpty(parentId)) {
|
||||
Log.w(TAG, "SubscribeCallback.onChildrenLoaded(): Ignoring empty parentId");
|
||||
return;
|
||||
@ -441,23 +444,26 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String parentId) {
|
||||
public void onError(@Nullable String parentId) {
|
||||
onErrorInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String parentId, Bundle options) {
|
||||
public void onError(@Nullable String parentId, @Nullable Bundle options) {
|
||||
onErrorInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children) {
|
||||
public void onChildrenLoaded(
|
||||
@Nullable String parentId, @Nullable List<MediaBrowserCompat.MediaItem> children) {
|
||||
onChildrenLoadedInternal(parentId, children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildrenLoaded(
|
||||
String parentId, List<MediaBrowserCompat.MediaItem> children, Bundle options) {
|
||||
@Nullable String parentId,
|
||||
@Nullable List<MediaBrowserCompat.MediaItem> children,
|
||||
Bundle options) {
|
||||
onChildrenLoadedInternal(parentId, children);
|
||||
}
|
||||
|
||||
@ -466,7 +472,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
private void onChildrenLoadedInternal(
|
||||
String parentId, List<MediaBrowserCompat.MediaItem> children) {
|
||||
@Nullable String parentId, @Nullable List<MediaBrowserCompat.MediaItem> children) {
|
||||
if (TextUtils.isEmpty(parentId)) {
|
||||
Log.w(TAG, "GetChildrenCallback.onChildrenLoaded(): Ignoring empty parentId");
|
||||
return;
|
||||
|
@ -19,12 +19,12 @@ import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
|
||||
/** Constants that can be shared between media session and controller. */
|
||||
public final class MediaConstants {
|
||||
@ -54,7 +54,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_MEDIA_ID_COMPAT =
|
||||
androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID;
|
||||
androidx.media3.session.legacy.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used for a localized error resolution string.
|
||||
@ -64,7 +64,8 @@ public final class MediaConstants {
|
||||
* service call.
|
||||
*/
|
||||
public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT =
|
||||
androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used for an error resolution intent.
|
||||
@ -74,7 +75,8 @@ public final class MediaConstants {
|
||||
* service call.
|
||||
*/
|
||||
public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT =
|
||||
androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to store a {@link PendingIntent}. When launched, the {@link
|
||||
@ -93,7 +95,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT_COMPAT =
|
||||
androidx.media.utils.MediaConstants
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT;
|
||||
|
||||
/**
|
||||
@ -110,7 +112,8 @@ public final class MediaConstants {
|
||||
* @see androidx.media3.common.Player#COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
|
||||
*/
|
||||
public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT =
|
||||
androidx.media.utils.MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key to indicate a preference that a region of space for the skip to previous
|
||||
@ -126,7 +129,8 @@ public final class MediaConstants {
|
||||
* @see androidx.media3.common.Player#COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
|
||||
*/
|
||||
public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV =
|
||||
androidx.media.utils.MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link MediaMetadata#extras} to indicate the playback completion
|
||||
@ -142,7 +146,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_COMPLETION_STATUS =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS;
|
||||
androidx.media3.session.legacy.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate that the corresponding
|
||||
@ -154,7 +158,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate that the corresponding
|
||||
@ -166,7 +171,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED =
|
||||
androidx.media.utils.MediaConstants
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED;
|
||||
|
||||
/**
|
||||
@ -179,7 +184,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link MediaMetadata#extras} to indicate an amount of completion
|
||||
@ -196,7 +202,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_COMPLETION_PERCENTAGE =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE;
|
||||
androidx.media3.session.legacy.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to indicate a preference about how playable instances of {@link
|
||||
@ -221,7 +227,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_CONTENT_STYLE_PLAYABLE =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE;
|
||||
androidx.media3.session.legacy.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to indicate a preference about how browsable instances of {@link
|
||||
@ -249,7 +255,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_CONTENT_STYLE_BROWSABLE =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE;
|
||||
androidx.media3.session.legacy.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link MediaMetadata#extras} to indicate a preference about how the
|
||||
@ -269,7 +275,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that certain
|
||||
@ -282,7 +289,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that certain
|
||||
@ -295,7 +303,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that
|
||||
@ -309,7 +318,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that
|
||||
@ -323,7 +333,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final int EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link MediaMetadata#extras} to indicate that certain instances of
|
||||
@ -339,7 +350,8 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE =
|
||||
androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE;
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link MediaMetadata#extras} to indicate that the corresponding
|
||||
@ -364,7 +376,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_IS_ADVERTISEMENT =
|
||||
androidx.media.utils.MediaConstants.METADATA_KEY_IS_ADVERTISEMENT;
|
||||
androidx.media3.session.legacy.MediaConstants.METADATA_KEY_IS_ADVERTISEMENT;
|
||||
|
||||
/**
|
||||
* {@link Bundle} value used to indicate the presence of an attribute described by its
|
||||
@ -388,7 +400,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_ROOT_CHILDREN_LIMIT =
|
||||
androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT;
|
||||
androidx.media3.session.legacy.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used in {@link LibraryParams#extras} passed to {@link
|
||||
@ -422,7 +434,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_MEDIA_ART_SIZE_PIXELS =
|
||||
androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS;
|
||||
androidx.media3.session.legacy.MediaConstants.BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS;
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to indicate that the media app that provides the service supports
|
||||
@ -446,7 +458,7 @@ public final class MediaConstants {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static final String EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT =
|
||||
androidx.media.utils.MediaConstants
|
||||
androidx.media3.session.legacy.MediaConstants
|
||||
.BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT;
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,6 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
@ -57,6 +56,7 @@ import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
@ -41,7 +41,6 @@ import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.util.Pair;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
@ -81,6 +80,7 @@ import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaController.MediaControllerImpl;
|
||||
import androidx.media3.session.PlayerInfo.BundlingExclusions;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
@ -33,13 +33,6 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.RatingCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Pair;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
@ -47,7 +40,6 @@ import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.VolumeProviderCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
@ -75,6 +67,14 @@ import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.LegacyConversions.ConversionException;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import androidx.media3.session.legacy.MediaControllerCompat;
|
||||
import androidx.media3.session.legacy.MediaMetadataCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import androidx.media3.session.legacy.RatingCompat;
|
||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@ -1824,7 +1824,10 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEvent(String event, Bundle extras) {
|
||||
public void onSessionEvent(@Nullable String event, @Nullable Bundle extras) {
|
||||
if (event == null) {
|
||||
return;
|
||||
}
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener ->
|
||||
@ -1832,11 +1835,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
listener.onCustomCommand(
|
||||
getInstance(),
|
||||
new SessionCommand(event, /* extras= */ Bundle.EMPTY),
|
||||
extras)));
|
||||
extras == null ? Bundle.EMPTY : extras)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state) {
|
||||
pendingLegacyPlayerInfo =
|
||||
pendingLegacyPlayerInfo.copyWithPlaybackStateCompat(
|
||||
convertToSafePlaybackStateCompat(state));
|
||||
@ -1844,26 +1847,26 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadataCompat metadata) {
|
||||
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata) {
|
||||
pendingLegacyPlayerInfo = pendingLegacyPlayerInfo.copyWithMediaMetadataCompat(metadata);
|
||||
startWaitingForPendingChanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged(@Nullable List<@NullableType QueueItem> queue) {
|
||||
public void onQueueChanged(@Nullable List<QueueItem> queue) {
|
||||
pendingLegacyPlayerInfo =
|
||||
pendingLegacyPlayerInfo.copyWithQueue(convertToNonNullQueueItemList(queue));
|
||||
startWaitingForPendingChanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueTitleChanged(CharSequence title) {
|
||||
public void onQueueTitleChanged(@Nullable CharSequence title) {
|
||||
pendingLegacyPlayerInfo = pendingLegacyPlayerInfo.copyWithQueueTitle(title);
|
||||
startWaitingForPendingChanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExtrasChanged(Bundle extras) {
|
||||
public void onExtrasChanged(@Nullable Bundle extras) {
|
||||
controllerInfo =
|
||||
new ControllerInfo(
|
||||
controllerInfo.playerInfo,
|
||||
@ -1876,7 +1879,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo newPlaybackInfo) {
|
||||
public void onAudioInfoChanged(@Nullable MediaControllerCompat.PlaybackInfo newPlaybackInfo) {
|
||||
pendingLegacyPlayerInfo = pendingLegacyPlayerInfo.copyWithPlaybackInfoCompat(newPlaybackInfo);
|
||||
startWaitingForPendingChanges();
|
||||
}
|
||||
@ -1928,7 +1931,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
LegacyPlayerInfo oldLegacyPlayerInfo,
|
||||
ControllerInfo oldControllerInfo,
|
||||
LegacyPlayerInfo newLegacyPlayerInfo,
|
||||
String sessionPackageName,
|
||||
@Nullable String sessionPackageName,
|
||||
long sessionFlags,
|
||||
boolean isSessionReady,
|
||||
@RatingCompat.Style int ratingType,
|
||||
@ -2580,12 +2583,12 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
SessionCommands availableSessionCommands,
|
||||
Commands availablePlayerCommands,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
Bundle sessionExtras) {
|
||||
@Nullable Bundle sessionExtras) {
|
||||
this.playerInfo = playerInfo;
|
||||
this.availableSessionCommands = availableSessionCommands;
|
||||
this.availablePlayerCommands = availablePlayerCommands;
|
||||
this.customLayout = customLayout;
|
||||
this.sessionExtras = sessionExtras;
|
||||
this.sessionExtras = sessionExtras == null ? Bundle.EMPTY : sessionExtras;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,28 +15,25 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE;
|
||||
import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE_SIZE;
|
||||
import static androidx.media.utils.MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||
import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
|
||||
import static androidx.media3.session.legacy.MediaBrowserCompat.EXTRA_PAGE;
|
||||
import static androidx.media3.session.legacy.MediaBrowserCompat.EXTRA_PAGE_SIZE;
|
||||
import static androidx.media3.session.legacy.MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.BadParcelableException;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
@ -46,6 +43,9 @@ import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.AsyncFunction;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
@ -82,7 +82,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Override
|
||||
@Nullable
|
||||
public BrowserRoot onGetRoot(
|
||||
String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
|
||||
@Nullable String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
|
||||
@Nullable BrowserRoot browserRoot = super.onGetRoot(clientPackageName, clientUid, rootHints);
|
||||
if (browserRoot == null) {
|
||||
return null;
|
||||
@ -140,7 +140,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
// content.
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onSubscribe(String id, Bundle option) {
|
||||
public void onSubscribe(@Nullable String id, @Nullable Bundle option) {
|
||||
@Nullable ControllerInfo controller = getCurrentController();
|
||||
if (controller == null) {
|
||||
return;
|
||||
@ -166,7 +166,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onUnsubscribe(String id) {
|
||||
public void onUnsubscribe(@Nullable String id) {
|
||||
@Nullable ControllerInfo controller = getCurrentController();
|
||||
if (controller == null) {
|
||||
return;
|
||||
@ -188,13 +188,14 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadChildren(String parentId, Result<List<MediaBrowserCompat.MediaItem>> result) {
|
||||
public void onLoadChildren(
|
||||
@Nullable String parentId, Result<List<MediaBrowserCompat.MediaItem>> result) {
|
||||
onLoadChildren(parentId, result, /* options= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadChildren(
|
||||
String parentId,
|
||||
@Nullable String parentId,
|
||||
Result<List<MediaBrowserCompat.MediaItem>> result,
|
||||
@Nullable Bundle options) {
|
||||
@Nullable ControllerInfo controller = getCurrentController();
|
||||
|
@ -29,7 +29,6 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
@ -41,6 +40,7 @@ import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -31,8 +31,6 @@ import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.GuardedBy;
|
||||
@ -40,7 +38,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
@ -64,6 +61,10 @@ import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.legacy.LegacyParcelableUtil;
|
||||
import androidx.media3.session.legacy.MediaControllerCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Longs;
|
||||
@ -628,9 +629,10 @@ public class MediaSession {
|
||||
* Bundle)} instead.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 RemoteUserInfo.
|
||||
@Deprecated
|
||||
public static ControllerInfo createTestOnlyControllerInfo(
|
||||
RemoteUserInfo remoteUserInfo,
|
||||
androidx.media.MediaSessionManager.RemoteUserInfo remoteUserInfo,
|
||||
int libraryVersion,
|
||||
int interfaceVersion,
|
||||
boolean trusted,
|
||||
@ -1144,10 +1146,27 @@ public class MediaSession {
|
||||
/**
|
||||
* Returns the legacy {@code android.support.v4.media.session.MediaSessionCompat.Token} of the
|
||||
* {@code android.support.v4.media.session.MediaSessionCompat} created internally by this session.
|
||||
*
|
||||
* @deprecated Use {@link #getPlatformToken()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@UnstableApi
|
||||
public final MediaSessionCompat.Token getSessionCompatToken() {
|
||||
return impl.getSessionCompat().getSessionToken();
|
||||
public final android.support.v4.media.session.MediaSessionCompat.Token getSessionCompatToken() {
|
||||
return LegacyParcelableUtil.convert(
|
||||
impl.getSessionCompat().getSessionToken(),
|
||||
android.support.v4.media.session.MediaSessionCompat.Token.CREATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform {@link android.media.session.MediaSession.Token} of the {@link
|
||||
* android.media.session.MediaSession} created internally by this session.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 token.
|
||||
@RequiresApi(21)
|
||||
@UnstableApi
|
||||
public final android.media.session.MediaSession.Token getPlatformToken() {
|
||||
return (android.media.session.MediaSession.Token)
|
||||
impl.getSessionCompat().getSessionToken().getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1165,6 +1184,7 @@ public class MediaSession {
|
||||
impl.connectFromService(controller, controllerInfo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/* package */ final IBinder getLegacyBrowserServiceBinder() {
|
||||
return impl.getLegacyBrowserServiceBinder();
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import androidx.annotation.CheckResult;
|
||||
@ -59,7 +58,6 @@ import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -85,6 +83,8 @@ import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
|
||||
import androidx.media3.session.SequencedFutureManager.SequencedFuture;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
@ -759,6 +759,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* Gets the service binder from the MediaBrowserServiceCompat. Should be only called by the thread
|
||||
* with a Looper.
|
||||
*/
|
||||
@Nullable
|
||||
protected IBinder getLegacyBrowserServiceBinder() {
|
||||
MediaSessionServiceLegacyStub legacyStub;
|
||||
synchronized (lock) {
|
||||
|
@ -57,21 +57,12 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.RatingCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media.VolumeProviderCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
@ -92,6 +83,15 @@ import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
|
||||
import androidx.media3.session.SessionCommand.CommandCode;
|
||||
import androidx.media3.session.legacy.MediaDescriptionCompat;
|
||||
import androidx.media3.session.legacy.MediaMetadataCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
|
||||
import androidx.media3.session.legacy.MediaSessionManager;
|
||||
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import androidx.media3.session.legacy.RatingCompat;
|
||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
@ -315,7 +315,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
public boolean onMediaButtonEvent(Intent intent) {
|
||||
return sessionImpl.onMediaButtonEvent(
|
||||
new ControllerInfo(
|
||||
sessionCompat.getCurrentControllerInfo(),
|
||||
checkNotNull(sessionCompat.getCurrentControllerInfo()),
|
||||
ControllerInfo.LEGACY_CONTROLLER_VERSION,
|
||||
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
|
||||
/* trusted= */ false,
|
||||
@ -353,7 +353,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareFromMediaId(String mediaId, @Nullable Bundle extras) {
|
||||
public void onPrepareFromMediaId(@Nullable String mediaId, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(
|
||||
mediaId, /* mediaUri= */ null, /* searchQuery= */ null, extras),
|
||||
@ -361,14 +361,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareFromSearch(String query, @Nullable Bundle extras) {
|
||||
public void onPrepareFromSearch(@Nullable String query, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(/* mediaId= */ null, /* mediaUri= */ null, query, extras),
|
||||
/* play= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareFromUri(Uri mediaUri, @Nullable Bundle extras) {
|
||||
public void onPrepareFromUri(@Nullable Uri mediaUri, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(
|
||||
/* mediaId= */ null, mediaUri, /* searchQuery= */ null, extras),
|
||||
@ -384,7 +384,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromMediaId(String mediaId, @Nullable Bundle extras) {
|
||||
public void onPlayFromMediaId(@Nullable String mediaId, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(
|
||||
mediaId, /* mediaUri= */ null, /* searchQuery= */ null, extras),
|
||||
@ -392,14 +392,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromSearch(String query, @Nullable Bundle extras) {
|
||||
public void onPlayFromSearch(@Nullable String query, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(/* mediaId= */ null, /* mediaUri= */ null, query, extras),
|
||||
/* play= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromUri(Uri mediaUri, @Nullable Bundle extras) {
|
||||
public void onPlayFromUri(@Nullable Uri mediaUri, @Nullable Bundle extras) {
|
||||
handleMediaRequest(
|
||||
createMediaItemForMediaRequest(
|
||||
/* mediaId= */ null, mediaUri, /* searchQuery= */ null, extras),
|
||||
@ -504,12 +504,12 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetRating(RatingCompat ratingCompat) {
|
||||
public void onSetRating(@Nullable RatingCompat ratingCompat) {
|
||||
onSetRating(ratingCompat, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetRating(RatingCompat ratingCompat, @Nullable Bundle unusedExtras) {
|
||||
public void onSetRating(@Nullable RatingCompat ratingCompat, @Nullable Bundle unusedExtras) {
|
||||
@Nullable Rating rating = LegacyConversions.convertToRating(ratingCompat);
|
||||
if (rating == null) {
|
||||
Log.w(TAG, "Ignoring invalid RatingCompat " + ratingCompat);
|
||||
@ -1442,7 +1442,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
@DoNotInline
|
||||
public static void setMediaButtonBroadcastReceiver(
|
||||
MediaSessionCompat mediaSessionCompat, ComponentName broadcastReceiver) {
|
||||
((android.media.session.MediaSession) mediaSessionCompat.getMediaSession())
|
||||
((android.media.session.MediaSession) checkNotNull(mediaSessionCompat.getMediaSession()))
|
||||
.setMediaButtonBroadcastReceiver(broadcastReceiver);
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +39,13 @@ import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.collection.ArrayMap;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionManager;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -18,15 +18,15 @@ package androidx.media3.session;
|
||||
import static androidx.media3.common.util.Util.postOrRun;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.media.MediaBrowserCompat.MediaItem;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media.MediaSessionManager.RemoteUserInfo;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat.MediaItem;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionManager;
|
||||
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@ -59,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Override
|
||||
@Nullable
|
||||
public BrowserRoot onGetRoot(
|
||||
String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
|
||||
@Nullable String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
|
||||
RemoteUserInfo info = getCurrentBrowserInfo();
|
||||
MediaSession.ControllerInfo controller =
|
||||
createControllerInfo(info, rootHints != null ? rootHints : Bundle.EMPTY);
|
||||
@ -89,7 +89,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadChildren(String parentId, Result<List<MediaItem>> result) {
|
||||
public void onLoadChildren(@Nullable String parentId, Result<List<MediaItem>> result) {
|
||||
result.sendResult(/* result= */ null);
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,6 @@ import android.text.TextUtils;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.BundleListRetriever;
|
||||
import androidx.media3.common.C;
|
||||
@ -88,6 +87,7 @@ import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
|
||||
import androidx.media3.session.SessionCommand.CommandCode;
|
||||
import androidx.media3.session.legacy.MediaSessionManager;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
@ -25,7 +25,6 @@ import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
import androidx.annotation.DoNotInline;
|
||||
@ -38,6 +37,7 @@ import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@ -262,7 +262,7 @@ public class MediaStyleNotificationHelper {
|
||||
if (actionsToShowInCompact != null) {
|
||||
int[] actions = actionsToShowInCompact;
|
||||
final int numActionsInCompact = Math.min(actions.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
|
||||
view.removeAllViews(androidx.media.R.id.media_actions);
|
||||
view.removeAllViews(androidx.media3.session.R.id.media_actions);
|
||||
if (numActionsInCompact > 0) {
|
||||
for (int i = 0; i < numActionsInCompact; i++) {
|
||||
if (i >= numActions) {
|
||||
@ -275,24 +275,25 @@ public class MediaStyleNotificationHelper {
|
||||
final androidx.core.app.NotificationCompat.Action action =
|
||||
mBuilder.mActions.get(actions[i]);
|
||||
final RemoteViews button = generateMediaActionButton(action);
|
||||
view.addView(androidx.media.R.id.media_actions, button);
|
||||
view.addView(androidx.media3.session.R.id.media_actions, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showCancelButton) {
|
||||
view.setViewVisibility(androidx.media.R.id.end_padder, View.GONE);
|
||||
view.setViewVisibility(androidx.media.R.id.cancel_action, View.VISIBLE);
|
||||
view.setOnClickPendingIntent(androidx.media.R.id.cancel_action, cancelButtonIntent);
|
||||
view.setViewVisibility(androidx.media3.session.R.id.end_padder, View.GONE);
|
||||
view.setViewVisibility(androidx.media3.session.R.id.cancel_action, View.VISIBLE);
|
||||
view.setOnClickPendingIntent(
|
||||
androidx.media3.session.R.id.cancel_action, cancelButtonIntent);
|
||||
view.setInt(
|
||||
androidx.media.R.id.cancel_action,
|
||||
androidx.media3.session.R.id.cancel_action,
|
||||
"setAlpha",
|
||||
mBuilder
|
||||
.mContext
|
||||
.getResources()
|
||||
.getInteger(androidx.media.R.integer.cancel_button_image_alpha));
|
||||
.getInteger(androidx.media3.session.R.integer.cancel_button_image_alpha));
|
||||
} else {
|
||||
view.setViewVisibility(androidx.media.R.id.end_padder, View.VISIBLE);
|
||||
view.setViewVisibility(androidx.media.R.id.cancel_action, View.GONE);
|
||||
view.setViewVisibility(androidx.media3.session.R.id.end_padder, View.VISIBLE);
|
||||
view.setViewVisibility(androidx.media3.session.R.id.cancel_action, View.GONE);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
@ -303,20 +304,21 @@ public class MediaStyleNotificationHelper {
|
||||
RemoteViews button =
|
||||
new RemoteViews(
|
||||
mBuilder.mContext.getPackageName(),
|
||||
androidx.media.R.layout.notification_media_action);
|
||||
androidx.media3.session.R.layout.media3_notification_media_action);
|
||||
IconCompat iconCompat = action.getIconCompat();
|
||||
if (iconCompat != null) {
|
||||
button.setImageViewResource(androidx.media.R.id.action0, iconCompat.getResId());
|
||||
button.setImageViewResource(androidx.media3.session.R.id.action0, iconCompat.getResId());
|
||||
}
|
||||
if (!tombstone) {
|
||||
button.setOnClickPendingIntent(androidx.media.R.id.action0, action.getActionIntent());
|
||||
button.setOnClickPendingIntent(
|
||||
androidx.media3.session.R.id.action0, action.getActionIntent());
|
||||
}
|
||||
button.setContentDescription(androidx.media.R.id.action0, action.getTitle());
|
||||
button.setContentDescription(androidx.media3.session.R.id.action0, action.getTitle());
|
||||
return button;
|
||||
}
|
||||
|
||||
/* package */ int getContentViewLayoutResource() {
|
||||
return androidx.media.R.layout.notification_template_media;
|
||||
return androidx.media3.session.R.layout.media3_notification_template_media;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -338,33 +340,33 @@ public class MediaStyleNotificationHelper {
|
||||
getBigContentViewLayoutResource(actionCount),
|
||||
/* fitIn1U= */ false);
|
||||
|
||||
big.removeAllViews(androidx.media.R.id.media_actions);
|
||||
big.removeAllViews(androidx.media3.session.R.id.media_actions);
|
||||
if (actionCount > 0) {
|
||||
for (int i = 0; i < actionCount; i++) {
|
||||
final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
|
||||
big.addView(androidx.media.R.id.media_actions, button);
|
||||
big.addView(androidx.media3.session.R.id.media_actions, button);
|
||||
}
|
||||
}
|
||||
if (showCancelButton) {
|
||||
big.setViewVisibility(androidx.media.R.id.cancel_action, View.VISIBLE);
|
||||
big.setViewVisibility(androidx.media3.session.R.id.cancel_action, View.VISIBLE);
|
||||
big.setInt(
|
||||
androidx.media.R.id.cancel_action,
|
||||
androidx.media3.session.R.id.cancel_action,
|
||||
"setAlpha",
|
||||
mBuilder
|
||||
.mContext
|
||||
.getResources()
|
||||
.getInteger(androidx.media.R.integer.cancel_button_image_alpha));
|
||||
big.setOnClickPendingIntent(androidx.media.R.id.cancel_action, cancelButtonIntent);
|
||||
.getInteger(androidx.media3.session.R.integer.cancel_button_image_alpha));
|
||||
big.setOnClickPendingIntent(androidx.media3.session.R.id.cancel_action, cancelButtonIntent);
|
||||
} else {
|
||||
big.setViewVisibility(androidx.media.R.id.cancel_action, View.GONE);
|
||||
big.setViewVisibility(androidx.media3.session.R.id.cancel_action, View.GONE);
|
||||
}
|
||||
return big;
|
||||
}
|
||||
|
||||
/* package */ int getBigContentViewLayoutResource(int actionCount) {
|
||||
return actionCount <= 3
|
||||
? androidx.media.R.layout.notification_template_big_media_narrow
|
||||
: androidx.media.R.layout.notification_template_big_media;
|
||||
? androidx.media3.session.R.layout.media3_notification_template_big_media_narrow
|
||||
: androidx.media3.session.R.layout.media3_notification_template_big_media;
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,9 +397,9 @@ public class MediaStyleNotificationHelper {
|
||||
* </pre>
|
||||
*
|
||||
* <p>If you are using this style, consider using the corresponding styles like {@link
|
||||
* androidx.media.R.style#TextAppearance_Compat_Notification_Media} or {@link
|
||||
* androidx.media.R.style#TextAppearance_Compat_Notification_Title_Media} in your custom views in
|
||||
* order to get the correct styling on each platform version.
|
||||
* androidx.media3.session.R.style#TextAppearance_Compat_Notification_Media} or {@link
|
||||
* androidx.media3.session.R.style#TextAppearance_Compat_Notification_Title_Media} in your custom
|
||||
* views in order to get the correct styling on each platform version.
|
||||
*
|
||||
* @see androidx.core.app.NotificationCompat.DecoratedCustomViewStyle
|
||||
* @see MediaStyle
|
||||
@ -469,7 +471,7 @@ public class MediaStyleNotificationHelper {
|
||||
@Override
|
||||
/* package */ int getContentViewLayoutResource() {
|
||||
return mBuilder.getContentView() != null
|
||||
? androidx.media.R.layout.notification_template_media_custom
|
||||
? androidx.media3.session.R.layout.media3_notification_template_media_custom
|
||||
: super.getContentViewLayoutResource();
|
||||
}
|
||||
|
||||
@ -500,8 +502,8 @@ public class MediaStyleNotificationHelper {
|
||||
@Override
|
||||
/* package */ int getBigContentViewLayoutResource(int actionCount) {
|
||||
return actionCount <= 3
|
||||
? androidx.media.R.layout.notification_template_big_media_narrow_custom
|
||||
: androidx.media.R.layout.notification_template_big_media_custom;
|
||||
? androidx.media3.session.R.layout.media3_notification_template_big_media_narrow_custom
|
||||
: androidx.media3.session.R.layout.media3_notification_template_big_media_custom;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -536,9 +538,12 @@ public class MediaStyleNotificationHelper {
|
||||
.mContext
|
||||
.getResources()
|
||||
.getColor(
|
||||
androidx.media.R.color.notification_material_background_media_default_color);
|
||||
androidx.media3.session.R.color
|
||||
.notification_material_background_media_default_color);
|
||||
views.setInt(
|
||||
androidx.media.R.id.status_bar_latest_event_content, "setBackgroundColor", color);
|
||||
androidx.media3.session.R.id.status_bar_latest_event_content,
|
||||
"setBackgroundColor",
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,9 @@ import static java.lang.Math.min;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Player.Command;
|
||||
@ -34,6 +32,8 @@ import androidx.media3.common.Player.Commands;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.PlayerInfo.BundlingExclusions;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat.BrowserRoot;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -28,14 +28,11 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.VolumeProviderCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
@ -53,6 +50,9 @@ import androidx.media3.common.text.CueGroup;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -18,13 +18,13 @@ package androidx.media3.session;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.legacy.MediaMetadataCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat.QueueItem;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
|
@ -29,19 +29,20 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcelable;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.media3.common.Bundleable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.legacy.LegacyParcelableUtil;
|
||||
import androidx.media3.session.legacy.MediaBrowserServiceCompat;
|
||||
import androidx.media3.session.legacy.MediaControllerCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
@ -262,49 +263,51 @@ public final class SessionToken implements Bundleable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token from a {@link android.media.session.MediaSession.Token}.
|
||||
* Creates a token from a {@link android.media.session.MediaSession.Token} or {@code
|
||||
* android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @param token The {@link android.media.session.MediaSession.Token}.
|
||||
* @param token The {@link android.media.session.MediaSession.Token} or {@code
|
||||
* android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
* @return A {@link ListenableFuture} for the {@link SessionToken}.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context, android.media.session.MediaSession.Token token) {
|
||||
return createSessionToken(context, MediaSessionCompat.Token.fromToken(token));
|
||||
Context context, Parcelable token) {
|
||||
return createSessionToken(context, createCompatToken(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token from a {@link android.media.session.MediaSession.Token}.
|
||||
* Creates a token from a {@link android.media.session.MediaSession.Token} or {@code
|
||||
* android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @param token The {@link android.media.session.MediaSession.Token}.
|
||||
* @param token The {@link android.media.session.MediaSession.Token} or {@code
|
||||
* android.support.v4.media.session.MediaSessionCompat.Token}..
|
||||
* @param completionLooper The {@link Looper} on which the returned {@link ListenableFuture}
|
||||
* completes. This {@link Looper} can't be used to call {@code future.get()} on the returned
|
||||
* {@link ListenableFuture}.
|
||||
* @return A {@link ListenableFuture} for the {@link SessionToken}.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context, android.media.session.MediaSession.Token token, Looper completionLooper) {
|
||||
return createSessionToken(context, MediaSessionCompat.Token.fromToken(token), completionLooper);
|
||||
Context context, Parcelable token, Looper completionLooper) {
|
||||
return createSessionToken(context, createCompatToken(token), completionLooper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token from a {@link android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @param compatToken The {@code android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
* @return A {@link ListenableFuture} for the {@link SessionToken}.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
|
||||
@UnstableApi
|
||||
public static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context, android.support.v4.media.session.MediaSessionCompat.Token compatToken) {
|
||||
private static MediaSessionCompat.Token createCompatToken(
|
||||
Parcelable platformOrLegacyCompatToken) {
|
||||
if (Util.SDK_INT >= 21
|
||||
&& platformOrLegacyCompatToken instanceof android.media.session.MediaSession.Token) {
|
||||
return MediaSessionCompat.Token.fromToken(platformOrLegacyCompatToken);
|
||||
}
|
||||
// Assume this is an android.support.v4.media.session.MediaSessionCompat.Token.
|
||||
return LegacyParcelableUtil.convert(
|
||||
platformOrLegacyCompatToken, MediaSessionCompat.Token.CREATOR);
|
||||
}
|
||||
|
||||
private static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context, MediaSessionCompat.Token compatToken) {
|
||||
HandlerThread thread = new HandlerThread("SessionTokenThread");
|
||||
thread.start();
|
||||
ListenableFuture<SessionToken> tokenFuture =
|
||||
@ -313,29 +316,15 @@ public final class SessionToken implements Bundleable {
|
||||
return tokenFuture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a token from a {@link android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @param compatToken The {@link android.support.v4.media.session.MediaSessionCompat.Token}.
|
||||
* @param completionLooper The {@link Looper} on which the returned {@link ListenableFuture}
|
||||
* completes. This {@link Looper} can't be used to call {@code future.get()} on the returned
|
||||
* {@link ListenableFuture}.
|
||||
* @return A {@link ListenableFuture} for the {@link SessionToken}.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
|
||||
@UnstableApi
|
||||
public static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context,
|
||||
android.support.v4.media.session.MediaSessionCompat.Token compatToken,
|
||||
Looper completionLooper) {
|
||||
private static ListenableFuture<SessionToken> createSessionToken(
|
||||
Context context, MediaSessionCompat.Token compatToken, Looper completionLooper) {
|
||||
checkNotNull(context, "context must not be null");
|
||||
checkNotNull(compatToken, "compatToken must not be null");
|
||||
|
||||
SettableFuture<SessionToken> future = SettableFuture.create();
|
||||
// Try retrieving media3 token by connecting to the session.
|
||||
MediaControllerCompat controller = new MediaControllerCompat(context, compatToken);
|
||||
String packageName = controller.getPackageName();
|
||||
String packageName = checkNotNull(controller.getPackageName());
|
||||
Handler handler = new Handler(completionLooper);
|
||||
Runnable createFallbackLegacyToken =
|
||||
() -> {
|
||||
|
@ -25,10 +25,10 @@ import static androidx.media3.session.SessionToken.TYPE_SESSION_LEGACY;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.session.SessionToken.SessionTokenImpl;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/* package */ final class SessionTokenImplLegacy implements SessionTokenImpl {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,596 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/**
|
||||
* Callback interface for a MediaSessionCompat to send updates to a MediaControllerCompat. This is
|
||||
* only used on pre-Lollipop systems.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public interface IMediaControllerCallback extends android.os.IInterface {
|
||||
/** Local-side IPC implementation stub class. */
|
||||
public abstract static class Stub extends android.os.Binder implements IMediaControllerCallback {
|
||||
private static final String DESCRIPTOR =
|
||||
"android.support.v4.media.session.IMediaControllerCallback";
|
||||
|
||||
/** Construct the stub at attach it to the interface. */
|
||||
// Using this in constructor
|
||||
@SuppressWarnings({"method.invocation.invalid", "argument.type.incompatible"})
|
||||
public Stub() {
|
||||
this.attachInterface(this, DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast an IBinder object into an androidx.media3.session.legacy.IMediaControllerCallback
|
||||
* interface, generating a proxy if needed.
|
||||
*/
|
||||
@Nullable
|
||||
public static IMediaControllerCallback asInterface(@Nullable android.os.IBinder obj) {
|
||||
if ((obj == null)) {
|
||||
return null;
|
||||
}
|
||||
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
|
||||
if (((iin != null) && (iin instanceof IMediaControllerCallback))) {
|
||||
return ((IMediaControllerCallback) iin);
|
||||
}
|
||||
return new Proxy(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(
|
||||
int code, android.os.Parcel data, @Nullable android.os.Parcel reply, int flags)
|
||||
throws android.os.RemoteException {
|
||||
String descriptor = DESCRIPTOR;
|
||||
switch (code) {
|
||||
case INTERFACE_TRANSACTION:
|
||||
{
|
||||
checkNotNull(reply).writeString(descriptor);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onEvent:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
String _arg0;
|
||||
_arg0 = data.readString();
|
||||
android.os.Bundle _arg1;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg1 = android.os.Bundle.CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg1 = null;
|
||||
}
|
||||
this.onEvent(_arg0, _arg1);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onSessionDestroyed:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
this.onSessionDestroyed();
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onPlaybackStateChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
PlaybackStateCompat _arg0;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg0 = PlaybackStateCompat.CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg0 = null;
|
||||
}
|
||||
this.onPlaybackStateChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onMetadataChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
MediaMetadataCompat _arg0;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg0 = MediaMetadataCompat.CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg0 = null;
|
||||
}
|
||||
this.onMetadataChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onQueueChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
java.util.List<MediaSessionCompat.QueueItem> _arg0;
|
||||
_arg0 = data.createTypedArrayList(MediaSessionCompat.QueueItem.CREATOR);
|
||||
this.onQueueChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onQueueTitleChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
CharSequence _arg0;
|
||||
if (0 != data.readInt()) {
|
||||
_arg0 = android.text.TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg0 = null;
|
||||
}
|
||||
this.onQueueTitleChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onExtrasChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
android.os.Bundle _arg0;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg0 = android.os.Bundle.CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg0 = null;
|
||||
}
|
||||
this.onExtrasChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onVolumeInfoChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
ParcelableVolumeInfo _arg0;
|
||||
if ((0 != data.readInt())) {
|
||||
_arg0 = ParcelableVolumeInfo.CREATOR.createFromParcel(data);
|
||||
} else {
|
||||
_arg0 = null;
|
||||
}
|
||||
this.onVolumeInfoChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onRepeatModeChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
int _arg0;
|
||||
_arg0 = data.readInt();
|
||||
this.onRepeatModeChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onShuffleModeChangedRemoved:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
boolean _arg0;
|
||||
_arg0 = (0 != data.readInt());
|
||||
this.onShuffleModeChangedRemoved(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onCaptioningEnabledChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
boolean _arg0;
|
||||
_arg0 = (0 != data.readInt());
|
||||
this.onCaptioningEnabledChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onShuffleModeChanged:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
int _arg0;
|
||||
_arg0 = data.readInt();
|
||||
this.onShuffleModeChanged(_arg0);
|
||||
return true;
|
||||
}
|
||||
case TRANSACTION_onSessionReady:
|
||||
{
|
||||
data.enforceInterface(descriptor);
|
||||
this.onSessionReady();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Proxy implements IMediaControllerCallback {
|
||||
private android.os.IBinder mRemote;
|
||||
|
||||
Proxy(android.os.IBinder remote) {
|
||||
mRemote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
public String getInterfaceDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(@Nullable String event, @Nullable android.os.Bundle extras)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeString(event);
|
||||
if ((extras != null)) {
|
||||
_data.writeInt(1);
|
||||
extras.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onEvent, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onEvent(event, extras);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed() throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onSessionDestroyed, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onSessionDestroyed();
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
// These callbacks are for the TransportController
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
if ((state != null)) {
|
||||
_data.writeInt(1);
|
||||
state.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onPlaybackStateChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onPlaybackStateChanged(state);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
if ((metadata != null)) {
|
||||
_data.writeInt(1);
|
||||
metadata.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onMetadataChanged, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onMetadataChanged(metadata);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged(@Nullable java.util.List<MediaSessionCompat.QueueItem> queue)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeTypedList(queue);
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onQueueChanged, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onQueueChanged(queue);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueTitleChanged(@Nullable CharSequence title)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
if (title != null) {
|
||||
_data.writeInt(1);
|
||||
android.text.TextUtils.writeToParcel(title, _data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onQueueTitleChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onQueueTitleChanged(title);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExtrasChanged(@Nullable android.os.Bundle extras)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
if ((extras != null)) {
|
||||
_data.writeInt(1);
|
||||
extras.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onExtrasChanged, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onExtrasChanged(extras);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info)
|
||||
throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
if ((info != null)) {
|
||||
_data.writeInt(1);
|
||||
info.writeToParcel(_data, 0);
|
||||
} else {
|
||||
_data.writeInt(0);
|
||||
}
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onVolumeInfoChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onVolumeInfoChanged(info);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int repeatMode) throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(repeatMode);
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onRepeatModeChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onRepeatModeChanged(repeatMode);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeChangedRemoved(boolean enabled) throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(((enabled) ? (1) : (0)));
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onShuffleModeChangedRemoved,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onShuffleModeChangedRemoved(enabled);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptioningEnabledChanged(boolean enabled) throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(((enabled) ? (1) : (0)));
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onCaptioningEnabledChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onCaptioningEnabledChanged(enabled);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeChanged(int shuffleMode) throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(shuffleMode);
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onShuffleModeChanged,
|
||||
_data,
|
||||
null,
|
||||
android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onShuffleModeChanged(shuffleMode);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionReady() throws android.os.RemoteException {
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
boolean _status =
|
||||
mRemote.transact(
|
||||
Stub.TRANSACTION_onSessionReady, _data, null, android.os.IBinder.FLAG_ONEWAY);
|
||||
if (!_status && getDefaultImpl() != null) {
|
||||
checkNotNull(getDefaultImpl()).onSessionReady();
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable public static IMediaControllerCallback sDefaultImpl;
|
||||
}
|
||||
|
||||
static final int TRANSACTION_onEvent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
|
||||
static final int TRANSACTION_onSessionDestroyed =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
|
||||
static final int TRANSACTION_onPlaybackStateChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
|
||||
static final int TRANSACTION_onMetadataChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
|
||||
static final int TRANSACTION_onQueueChanged = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
|
||||
static final int TRANSACTION_onQueueTitleChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
|
||||
static final int TRANSACTION_onExtrasChanged = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6);
|
||||
static final int TRANSACTION_onVolumeInfoChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 7);
|
||||
static final int TRANSACTION_onRepeatModeChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 8);
|
||||
static final int TRANSACTION_onShuffleModeChangedRemoved =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 9);
|
||||
static final int TRANSACTION_onCaptioningEnabledChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 10);
|
||||
static final int TRANSACTION_onShuffleModeChanged =
|
||||
(android.os.IBinder.FIRST_CALL_TRANSACTION + 11);
|
||||
static final int TRANSACTION_onSessionReady = (android.os.IBinder.FIRST_CALL_TRANSACTION + 12);
|
||||
|
||||
public static boolean setDefaultImpl(IMediaControllerCallback impl) {
|
||||
// Only one user of this interface can use this function
|
||||
// at a time. This is a heuristic to detect if two different
|
||||
// users in the same process use this function.
|
||||
if (Proxy.sDefaultImpl != null) {
|
||||
throw new IllegalStateException("setDefaultImpl() called twice");
|
||||
}
|
||||
if (impl != null) {
|
||||
Proxy.sDefaultImpl = impl;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IMediaControllerCallback getDefaultImpl() {
|
||||
return Proxy.sDefaultImpl;
|
||||
}
|
||||
}
|
||||
|
||||
public void onEvent(@Nullable String event, @Nullable android.os.Bundle extras)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void onSessionDestroyed() throws android.os.RemoteException;
|
||||
|
||||
// These callbacks are for the TransportController
|
||||
|
||||
public void onPlaybackStateChanged(@Nullable PlaybackStateCompat state)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void onMetadataChanged(@Nullable MediaMetadataCompat metadata)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void onQueueChanged(@Nullable java.util.List<MediaSessionCompat.QueueItem> queue)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void onQueueTitleChanged(@Nullable CharSequence title) throws android.os.RemoteException;
|
||||
|
||||
public void onExtrasChanged(@Nullable android.os.Bundle extras) throws android.os.RemoteException;
|
||||
|
||||
public void onVolumeInfoChanged(@Nullable ParcelableVolumeInfo info)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public void onRepeatModeChanged(int repeatMode) throws android.os.RemoteException;
|
||||
|
||||
public void onShuffleModeChangedRemoved(boolean enabled) throws android.os.RemoteException;
|
||||
|
||||
public void onCaptioningEnabledChanged(boolean enabled) throws android.os.RemoteException;
|
||||
|
||||
public void onShuffleModeChanged(int shuffleMode) throws android.os.RemoteException;
|
||||
|
||||
public void onSessionReady() throws android.os.RemoteException;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
|
||||
/**
|
||||
* Utilities to convert {@link android.os.Parcelable} instances to and from legacy package names
|
||||
* when writing to or reading them from a {@link android.os.Bundle}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public final class LegacyParcelableUtil {
|
||||
|
||||
private LegacyParcelableUtil() {}
|
||||
|
||||
/**
|
||||
* Converts one {@link Parcelable} to another assuming they both share the same parcel structure.
|
||||
*
|
||||
* @param value The input {@link Parcelable}.
|
||||
* @param creator The {@link Parcelable.Creator} of the output type.
|
||||
* @return The output {@link Parcelable}.
|
||||
* @param <T> The output type.
|
||||
* @param <U> The input type.
|
||||
*/
|
||||
public static <T extends Parcelable, U extends Parcelable> @PolyNull T convert(
|
||||
@PolyNull U value, Parcelable.Creator<T> creator) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
value = maybeApplyMediaDescriptionParcelableBugWorkaround(value);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
try {
|
||||
value.writeToParcel(parcel, /* flags= */ 0);
|
||||
parcel.setDataPosition(0);
|
||||
T result = creator.createFromParcel(parcel);
|
||||
result = maybeApplyMediaDescriptionParcelableBugWorkaround(result);
|
||||
return result;
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts one {@link Parcelable} {@link List} to another assuming they both share the same
|
||||
* parcel structure.
|
||||
*
|
||||
* @param value The input {@link Parcelable} {@link List}.
|
||||
* @param creator The {@link Parcelable.Creator} of the output type.
|
||||
* @return The output {@link Parcelable} {@link ArrayList}.
|
||||
* @param <T> The output type.
|
||||
* @param <U> The input type.
|
||||
*/
|
||||
public static <T extends Parcelable, U extends Parcelable> @PolyNull ArrayList<T> convertList(
|
||||
@PolyNull List<U> value, Parcelable.Creator<T> creator) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<T> output = new ArrayList<>();
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
output.add(convert(value.get(i), creator));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// TODO: b/335804969 - Remove this workaround once the bug fix is in the androidx.media dependency
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T maybeApplyMediaDescriptionParcelableBugWorkaround(T value) {
|
||||
if (Util.SDK_INT < 21 || Util.SDK_INT >= 23) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof android.support.v4.media.MediaBrowserCompat.MediaItem) {
|
||||
android.support.v4.media.MediaBrowserCompat.MediaItem mediaItem =
|
||||
(android.support.v4.media.MediaBrowserCompat.MediaItem) value;
|
||||
return (T)
|
||||
new android.support.v4.media.MediaBrowserCompat.MediaItem(
|
||||
rebuildMediaDescriptionCompat(mediaItem.getDescription()), mediaItem.getFlags());
|
||||
} else if (value instanceof android.support.v4.media.MediaDescriptionCompat) {
|
||||
android.support.v4.media.MediaDescriptionCompat description =
|
||||
(android.support.v4.media.MediaDescriptionCompat) value;
|
||||
return (T) rebuildMediaDescriptionCompat(description);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static android.support.v4.media.MediaDescriptionCompat rebuildMediaDescriptionCompat(
|
||||
android.support.v4.media.MediaDescriptionCompat value) {
|
||||
return new android.support.v4.media.MediaDescriptionCompat.Builder()
|
||||
.setMediaId(value.getMediaId())
|
||||
.setTitle(value.getTitle())
|
||||
.setSubtitle(value.getSubtitle())
|
||||
.setDescription(value.getDescription())
|
||||
.setIconBitmap(value.getIconBitmap())
|
||||
.setIconUri(value.getIconUri())
|
||||
.setExtras(value.getExtras())
|
||||
.setMediaUri(value.getMediaUri())
|
||||
.build();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/** */
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public class MediaBrowserCompatUtils {
|
||||
public static boolean areSameOptions(@Nullable Bundle options1, @Nullable Bundle options2) {
|
||||
if (options1 == options2) {
|
||||
return true;
|
||||
} else if (options1 == null) {
|
||||
checkStateNotNull(options2);
|
||||
return options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1) == -1
|
||||
&& options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1) == -1;
|
||||
} else if (options2 == null && options1 != null) {
|
||||
checkStateNotNull(options1);
|
||||
return options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1) == -1
|
||||
&& options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1) == -1;
|
||||
} else {
|
||||
checkStateNotNull(options1);
|
||||
checkStateNotNull(options2);
|
||||
return options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1)
|
||||
== options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1)
|
||||
&& options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1)
|
||||
== options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasDuplicatedItems(@Nullable Bundle options1, @Nullable Bundle options2) {
|
||||
int page1 = options1 == null ? -1 : options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
|
||||
int page2 = options2 == null ? -1 : options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
|
||||
int pageSize1 = options1 == null ? -1 : options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
|
||||
int pageSize2 = options2 == null ? -1 : options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
|
||||
|
||||
int startIndex1, startIndex2, endIndex1, endIndex2;
|
||||
if (page1 == -1 || pageSize1 == -1) {
|
||||
startIndex1 = 0;
|
||||
endIndex1 = Integer.MAX_VALUE;
|
||||
} else {
|
||||
startIndex1 = pageSize1 * page1;
|
||||
endIndex1 = startIndex1 + pageSize1 - 1;
|
||||
}
|
||||
|
||||
if (page2 == -1 || pageSize2 == -1) {
|
||||
startIndex2 = 0;
|
||||
endIndex2 = Integer.MAX_VALUE;
|
||||
} else {
|
||||
startIndex2 = pageSize2 * page2;
|
||||
endIndex2 = startIndex2 + pageSize2 - 1;
|
||||
}
|
||||
|
||||
// For better readability, leaving the exclamation mark here.
|
||||
return !(endIndex1 < startIndex2 || endIndex2 < startIndex1);
|
||||
}
|
||||
|
||||
private MediaBrowserCompatUtils() {}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/** Defines the communication protocol for media browsers and media browser services. */
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public class MediaBrowserProtocol {
|
||||
|
||||
public static final String DATA_CALLBACK_TOKEN = "data_callback_token";
|
||||
public static final String DATA_CALLING_UID = "data_calling_uid";
|
||||
public static final String DATA_CALLING_PID = "data_calling_pid";
|
||||
public static final String DATA_MEDIA_ITEM_ID = "data_media_item_id";
|
||||
public static final String DATA_MEDIA_ITEM_LIST = "data_media_item_list";
|
||||
public static final String DATA_MEDIA_SESSION_TOKEN = "data_media_session_token";
|
||||
public static final String DATA_OPTIONS = "data_options";
|
||||
public static final String DATA_NOTIFY_CHILDREN_CHANGED_OPTIONS =
|
||||
"data_notify_children_changed_options";
|
||||
public static final String DATA_PACKAGE_NAME = "data_package_name";
|
||||
public static final String DATA_RESULT_RECEIVER = "data_result_receiver";
|
||||
public static final String DATA_ROOT_HINTS = "data_root_hints";
|
||||
public static final String DATA_SEARCH_EXTRAS = "data_search_extras";
|
||||
public static final String DATA_SEARCH_QUERY = "data_search_query";
|
||||
public static final String DATA_CUSTOM_ACTION = "data_custom_action";
|
||||
public static final String DATA_CUSTOM_ACTION_EXTRAS = "data_custom_action_extras";
|
||||
|
||||
public static final String EXTRA_CLIENT_VERSION = "extra_client_version";
|
||||
public static final String EXTRA_CALLING_PID = "extra_calling_pid";
|
||||
public static final String EXTRA_SERVICE_VERSION = "extra_service_version";
|
||||
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
|
||||
* is introduced so that the browser can distinguish whether the service supports subscribing
|
||||
* through compat code.
|
||||
*/
|
||||
public static final int SERVICE_VERSION_2 = 2;
|
||||
|
||||
public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_2;
|
||||
|
||||
/*
|
||||
* Messages sent from the media browser service compat to the media browser compat.
|
||||
* (Compat implementation for IMediaBrowserServiceCallbacks)
|
||||
* DO NOT RENUMBER THESE!
|
||||
*/
|
||||
|
||||
/**
|
||||
* (service v1) Sent after {@link MediaBrowserCompat#connect()} when the request has successfully
|
||||
* completed. - arg1 : The service version - data DATA_MEDIA_ITEM_ID : A string for the root media
|
||||
* item id DATA_MEDIA_SESSION_TOKEN : Media session token DATA_ROOT_HINTS : An optional root hints
|
||||
* bundle of service-specific arguments
|
||||
*/
|
||||
public static final int SERVICE_MSG_ON_CONNECT = 1;
|
||||
|
||||
/**
|
||||
* (service v1) Sent after {@link MediaBrowserCompat#connect()} when the connection to the media
|
||||
* browser failed. - arg1 : service version
|
||||
*/
|
||||
public static final int SERVICE_MSG_ON_CONNECT_FAILED = 2;
|
||||
|
||||
/**
|
||||
* (service v1) Sent when the list of children is loaded or updated. - arg1 : The service version
|
||||
* - data DATA_MEDIA_ITEM_ID : A string for the parent media item id DATA_MEDIA_ITEM_LIST : An
|
||||
* array list for the media item children DATA_OPTIONS : A bundle of service-specific arguments
|
||||
* sent from the media browse to the media browser service DATA_NOTIFY_CHILDREN_CHANGED_OPTIONS :
|
||||
* A bundle of service-specific arguments sent from the media browser service to the media browser
|
||||
* by calling {@link MediaBrowserServiceCompat#notifyChildrenChanged(String, Bundle)}
|
||||
*/
|
||||
public static final int SERVICE_MSG_ON_LOAD_CHILDREN = 3;
|
||||
|
||||
/**
|
||||
* MediaBrowserServiceCompat will check the version of the MediaBrowserCompat, and it will not
|
||||
* send messages if they are introduced in the higher version of the MediaBrowserCompat.
|
||||
*/
|
||||
public static final int CLIENT_VERSION_1 = 1;
|
||||
|
||||
public static final int CLIENT_VERSION_CURRENT = CLIENT_VERSION_1;
|
||||
|
||||
/*
|
||||
* Messages sent from the media browser compat to the media browser service compat.
|
||||
* (Compat implementation for IMediaBrowserService)
|
||||
* DO NOT RENUMBER THESE!
|
||||
*/
|
||||
|
||||
/**
|
||||
* (client v1) Sent to connect to the media browse service compat. - arg1 : The client version -
|
||||
* data DATA_PACKAGE_NAME : A string for the package name of MediaBrowserCompat DATA_ROOT_HINTS :
|
||||
* An optional root hints bundle of service-specific arguments - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_CONNECT = 1;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to disconnect from the media browse service compat. - arg1 : The client
|
||||
* version - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_DISCONNECT = 2;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to subscribe for changes to the children of the specified media id. - arg1 :
|
||||
* The client version - data DATA_MEDIA_ITEM_ID : A string for a media item id DATA_OPTIONS : A
|
||||
* bundle of service-specific arguments sent from the media browser to the media browser service
|
||||
* DATA_CALLBACK_TOKEN : An IBinder of service-specific arguments sent from the media browser to
|
||||
* the media browser service - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_ADD_SUBSCRIPTION = 3;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to unsubscribe for changes to the children of the specified media id. - arg1 :
|
||||
* The client version - data DATA_MEDIA_ITEM_ID : A string for a media item id DATA_CALLBACK_TOKEN
|
||||
* : An IBinder of service-specific arguments sent from the media browser to the media browser
|
||||
* service - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_REMOVE_SUBSCRIPTION = 4;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to retrieve a specific media item from the connected service. - arg1 : The
|
||||
* client version - data DATA_MEDIA_ITEM_ID : A string for a media item id DATA_RESULT_RECEIVER :
|
||||
* Result receiver to get the result - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_GET_MEDIA_ITEM = 5;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to register the client messenger - arg1 : The client version - data
|
||||
* DATA_ROOT_HINTS : An optional root hints bundle of service-specific arguments - replyTo :
|
||||
* Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_REGISTER_CALLBACK_MESSENGER = 6;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to unregister the client messenger - arg1 : The client version - replyTo :
|
||||
* Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER = 7;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to retrieve a specific media item from the connected service. - arg1 : The
|
||||
* client version - data DATA_SEARCH_QUERY : A string for search query that contains keywords
|
||||
* separated by space. DATA_SEARCH_EXTRAS : A bundle of service-specific arguments to send to the
|
||||
* media browser service. DATA_RESULT_RECEIVER : Result receiver to get the result. - replyTo :
|
||||
* Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_SEARCH = 8;
|
||||
|
||||
/**
|
||||
* (client v1) Sent to request a custom action from the media browser. - arg1 : The client version
|
||||
* - data DATA_CUSTOM_ACTION : A string for the custom action. DATA_CUSTOM_ACTION_EXTRAS : A
|
||||
* bundle of service-specific arguments to send to the media browser service. DATA_RESULT_RECEIVER
|
||||
* : Result receiver to get the result. - replyTo : Callback messenger
|
||||
*/
|
||||
public static final int CLIENT_MSG_SEND_CUSTOM_ACTION = 9;
|
||||
|
||||
private MediaBrowserProtocol() {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,397 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
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;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A media button receiver receives and helps translate hardware media playback buttons, such as
|
||||
* those found on wired and wireless headsets, into the appropriate callbacks in your app.
|
||||
*
|
||||
* <p>You can add this MediaButtonReceiver to your app by adding it directly to your
|
||||
* AndroidManifest.xml:
|
||||
*
|
||||
* <pre>
|
||||
* <receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
* <intent-filter>
|
||||
* <action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
* </intent-filter>
|
||||
* </receiver>
|
||||
* </pre>
|
||||
*
|
||||
* This class assumes you have a {@link Service} in your app that controls media playback via a
|
||||
* {@link MediaSessionCompat}. Once a key event is received by MediaButtonReceiver, this class tries
|
||||
* to find a {@link Service} that can handle {@link Intent#ACTION_MEDIA_BUTTON}, and a {@link
|
||||
* MediaBrowserServiceCompat} in turn. If an appropriate service is found, this class forwards the
|
||||
* key event to the service. If neither is available or more than one valid service/media browser
|
||||
* service is found, an {@link IllegalStateException} will be thrown. Thus, your app should have one
|
||||
* of the following services to get a key event properly.
|
||||
*
|
||||
* <h2>Service Handling ACTION_MEDIA_BUTTON</h2>
|
||||
*
|
||||
* A service can receive a key event by including an intent filter that handles {@link
|
||||
* Intent#ACTION_MEDIA_BUTTON}:
|
||||
*
|
||||
* <pre>
|
||||
* <service android:name="com.example.android.MediaPlaybackService" >
|
||||
* <intent-filter>
|
||||
* <action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
* </intent-filter>
|
||||
* </service>
|
||||
* </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.
|
||||
*
|
||||
* <h2>MediaBrowserService</h2>
|
||||
*
|
||||
* If you already have a {@link MediaBrowserServiceCompat} in your app, MediaButtonReceiver will
|
||||
* deliver the received key events to the {@link MediaBrowserServiceCompat} by default. You can
|
||||
* handle them in your {@link MediaSessionCompat.Callback}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "MediaButtonReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null
|
||||
|| !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|
||||
|| !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
|
||||
Log.d(TAG, "Ignore unsupported intent: " + intent);
|
||||
return;
|
||||
}
|
||||
ComponentName mediaButtonServiceComponentName =
|
||||
getServiceComponentByAction(context, Intent.ACTION_MEDIA_BUTTON);
|
||||
if (mediaButtonServiceComponentName != null) {
|
||||
intent.setComponent(mediaButtonServiceComponentName);
|
||||
try {
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
} catch (/* ForegroundServiceStartNotAllowedException */ IllegalStateException e) {
|
||||
if (Build.VERSION.SDK_INT >= 31
|
||||
&& Api31.instanceOfForegroundServiceStartNotAllowedException(e)) {
|
||||
onForegroundServiceStartNotAllowedException(
|
||||
intent, Api31.castToForegroundServiceStartNotAllowedException(e));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ComponentName mediaBrowserServiceComponentName =
|
||||
getServiceComponentByAction(context, MediaBrowserServiceCompat.SERVICE_INTERFACE);
|
||||
if (mediaBrowserServiceComponentName != null) {
|
||||
PendingResult pendingResult = goAsync();
|
||||
Context applicationContext = context.getApplicationContext();
|
||||
MediaButtonConnectionCallback connectionCallback =
|
||||
new MediaButtonConnectionCallback(applicationContext, intent, pendingResult);
|
||||
MediaBrowserCompat mediaBrowser =
|
||||
new MediaBrowserCompat(
|
||||
applicationContext, mediaBrowserServiceComponentName, connectionCallback, null);
|
||||
connectionCallback.setMediaBrowser(mediaBrowser);
|
||||
mediaBrowser.connect();
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Could not find any Service that handles "
|
||||
+ Intent.ACTION_MEDIA_BUTTON
|
||||
+ " or implements a media browser service.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when an exception is thrown when calling {@link
|
||||
* Context#startForegroundService(Intent)} as a result of receiving a media button event.
|
||||
*
|
||||
* <p>By default, this method only logs the exception and it can be safely overridden. Apps that
|
||||
* find that such a media button event has been legitimately sent, may choose to override this
|
||||
* method and take the opportunity to post a notification from where the user journey can
|
||||
* continue.
|
||||
*
|
||||
* <p>This exception can be thrown if a broadcast media button event is received and a media
|
||||
* service is found in the manifest that is registered to handle {@link
|
||||
* Intent#ACTION_MEDIA_BUTTON}. If this happens on API 31+ and the app is in the background then
|
||||
* an exception is thrown.
|
||||
*
|
||||
* <p>Normally, a media button intent should only be required to be sent by the system in case of
|
||||
* a Bluetooth media button event that wants to restart the app. However, in such a case the app
|
||||
* gets an exemption and is allowed to start the foreground service. In this case this method will
|
||||
* never be called.
|
||||
*
|
||||
* <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) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"caught exception when trying to start a foreground service from the "
|
||||
+ "background: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
|
||||
private static class MediaButtonConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
|
||||
private final Context mContext;
|
||||
private final Intent mIntent;
|
||||
private final PendingResult mPendingResult;
|
||||
|
||||
@Nullable private MediaBrowserCompat mMediaBrowser;
|
||||
|
||||
MediaButtonConnectionCallback(Context context, Intent intent, PendingResult pendingResult) {
|
||||
mContext = context;
|
||||
mIntent = intent;
|
||||
mPendingResult = pendingResult;
|
||||
}
|
||||
|
||||
void setMediaBrowser(MediaBrowserCompat mediaBrowser) {
|
||||
mMediaBrowser = mediaBrowser;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onConnected() {
|
||||
MediaControllerCompat mediaController =
|
||||
new MediaControllerCompat(mContext, checkNotNull(mMediaBrowser).getSessionToken());
|
||||
KeyEvent ke = mIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||
mediaController.dispatchMediaButtonEvent(ke);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
checkNotNull(mMediaBrowser).disconnect();
|
||||
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
|
||||
public static ComponentName getMediaButtonReceiverComponent(Context context) {
|
||||
Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
queryIntent.setPackage(context.getPackageName());
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
|
||||
if (resolveInfos.size() == 1) {
|
||||
ResolveInfo resolveInfo = resolveInfos.get(0);
|
||||
return new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||
} else if (resolveInfos.size() > 1) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"More than one BroadcastReceiver that handles "
|
||||
+ Intent.ACTION_MEDIA_BUTTON
|
||||
+ " was found, returning null.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
private static ComponentName getServiceComponentByAction(Context context, String action) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
Intent queryIntent = new Intent(action);
|
||||
queryIntent.setPackage(context.getPackageName());
|
||||
List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0 /* flags */);
|
||||
if (resolveInfos.size() == 1) {
|
||||
ResolveInfo resolveInfo = resolveInfos.get(0);
|
||||
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
|
||||
} else if (resolveInfos.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Expected 1 service that handles " + action + ", found " + resolveInfos.size());
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(31)
|
||||
private static final class Api31 {
|
||||
/**
|
||||
* Returns true if the passed exception is a {@link ForegroundServiceStartNotAllowedException}.
|
||||
*/
|
||||
@DoNotInline
|
||||
public static boolean instanceOfForegroundServiceStartNotAllowedException(
|
||||
IllegalStateException e) {
|
||||
return e instanceof ForegroundServiceStartNotAllowedException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts the {@link IllegalStateException} to a {@link
|
||||
* ForegroundServiceStartNotAllowedException} and throws an exception if the cast fails.
|
||||
*/
|
||||
@DoNotInline
|
||||
public static ForegroundServiceStartNotAllowedException
|
||||
castToForegroundServiceStartNotAllowedException(IllegalStateException e) {
|
||||
return (ForegroundServiceStartNotAllowedException) e;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,926 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Media constants for sharing constants between media provider and consumer apps */
|
||||
@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}
|
||||
* to indicate a preference that a region of space for the skip to next control should always be
|
||||
* blocked out in the UI, even when the {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT skip to
|
||||
* next standard action} is not supported. This may be used when the session temporarily hides
|
||||
* skip to next by design.
|
||||
*
|
||||
* <p>TYPE: boolean
|
||||
*
|
||||
* @see MediaControllerCompat#getExtras()
|
||||
* @see MediaSessionCompat#setExtras(Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT =
|
||||
"android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
|
||||
|
||||
/**
|
||||
* Bundle key passed from {@link MediaSessionCompat} to the hosting {@link MediaControllerCompat}
|
||||
* to indicate a preference that a region of space for the skip to previous control should always
|
||||
* be blocked out in the UI, even when the {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS skip
|
||||
* to previous standard action} is not supported. This may be used when the session temporarily
|
||||
* hides skip to previous by design.
|
||||
*
|
||||
* <p>TYPE: boolean
|
||||
*
|
||||
* @see MediaControllerCompat#getExtras()
|
||||
* @see MediaSessionCompat#setExtras(Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
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()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
*/
|
||||
@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
|
||||
* MediaBrowserCompat}. Excess root children may be omitted or made less discoverable by the host.
|
||||
*
|
||||
* <p>TYPE: int
|
||||
*
|
||||
* @see MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)
|
||||
* @see MediaBrowserServiceCompat#getBrowserRootHints()
|
||||
* @see MediaBrowserCompat#MediaBrowserCompat(Context,ComponentName,ConnectionCallback,Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT =
|
||||
"androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_LIMIT";
|
||||
|
||||
/**
|
||||
* Bundle key passed through root hints to the {@link MediaBrowserServiceCompat} to indicate which
|
||||
* flags exposed by {@link MediaBrowserCompat.MediaItem#getFlags()} from children of the root node
|
||||
* are supported by the hosting {@link MediaBrowserCompat}. Root children with unsupported flags
|
||||
* may be omitted or made less discoverable by the host.
|
||||
*
|
||||
* <p>TYPE: int, a bit field which can be used as a mask. For example, if the value masked (using
|
||||
* bitwise AND) with {@link MediaBrowserCompat.MediaItem#FLAG_BROWSABLE} is nonzero, then the host
|
||||
* supports browsable root children. Conversely, if the masked result is zero, then the host does
|
||||
* not support them.
|
||||
*
|
||||
* @see MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)
|
||||
* @see MediaBrowserServiceCompat#getBrowserRootHints()
|
||||
* @see MediaBrowserCompat#MediaBrowserCompat(Context,ComponentName,ConnectionCallback,Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS =
|
||||
"androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_SUPPORTED_FLAGS";
|
||||
|
||||
/**
|
||||
* Bundle key passed through root hints to the {@link MediaBrowserServiceCompat} to indicate the
|
||||
* recommended size, in pixels, for media art bitmaps. Much smaller images may not render well,
|
||||
* and much larger images may cause inefficient resource consumption.
|
||||
*
|
||||
* @see MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)
|
||||
* @see MediaBrowserServiceCompat#getBrowserRootHints()
|
||||
* @see MediaBrowserCompat#MediaBrowserCompat(Context,ComponentName,ConnectionCallback,Bundle)
|
||||
* @see MediaDescriptionCompat#getIconUri()
|
||||
* @see MediaDescriptionCompat.Builder#setIconUri(Uri)
|
||||
* @see MediaDescriptionCompat#getIconBitmap()
|
||||
* @see MediaDescriptionCompat.Builder#setIconBitmap(Bitmap)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS =
|
||||
"android.media.extras.MEDIA_ART_SIZE_HINT_PIXELS";
|
||||
|
||||
/**
|
||||
* Bundle key used to indicate that the {@link MediaBrowserServiceCompat} supports showing a
|
||||
* settings page.
|
||||
*
|
||||
* <p>Use this key to populate the {@link Bundle} that you pass to the constructor of the {@link
|
||||
* MediaBrowserServiceCompat.BrowserRoot} returned by {@link MediaBrowserServiceCompat#onGetRoot}.
|
||||
* Use {@link Bundle#putParcelable(String, Parcelable)} to set a {@link PendingIntent} for this
|
||||
* key. The {@link PendingIntent} is created using the {@code CarPendingIntent#getCarApp()}
|
||||
* method.
|
||||
*
|
||||
* <p>The {@link Intent} carried by the pending intent needs to have the component name set to a
|
||||
* <a href="http://developer.android.com/training/cars/apps#create-carappservice">Car App Library
|
||||
* service</a> that needs to exist in the same application package as the media browser service.
|
||||
*
|
||||
* <p>TYPE: {@link PendingIntent}.
|
||||
*
|
||||
* @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_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT =
|
||||
"androidx.media.BrowserRoot.Extras"
|
||||
+ ".APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT";
|
||||
|
||||
/**
|
||||
* Bundle key sent through {@link MediaBrowserCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate that the {@link MediaBrowserServiceCompat} supports the method
|
||||
* {@link MediaBrowserServiceCompat#onSearch(String, Bundle, MediaBrowserServiceCompat.Result)}.
|
||||
* If sent as {@code true}, the host may expose affordances which call the search method.
|
||||
*
|
||||
* <p>TYPE: boolean
|
||||
*
|
||||
* @see MediaBrowserCompat#getExtras()
|
||||
* @see MediaBrowserServiceCompat.BrowserRoot#BrowserRoot(String, Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
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
|
||||
* MediaBrowserCompat.MediaItem} are presented.
|
||||
*
|
||||
* <p>If exposed through {@link MediaBrowserCompat#getExtras()}, the preference applies to all
|
||||
* playable items within the browse tree.
|
||||
*
|
||||
* <p>If exposed through {@link MediaDescriptionCompat#getExtras()}, the preference applies to
|
||||
* only the immediate playable children of the corresponding browsable item. It takes precedence
|
||||
* over preferences sent through {@link MediaBrowserCompat#getExtras()}.
|
||||
*
|
||||
* <p>TYPE: int. Possible values are separate constants.
|
||||
*
|
||||
* @see MediaBrowserCompat#getExtras()
|
||||
* @see MediaBrowserServiceCompat.BrowserRoot#BrowserRoot(String, Bundle)
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE =
|
||||
"android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
|
||||
|
||||
/**
|
||||
* Bundle key passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference about how browsable instances of {@link
|
||||
* MediaBrowserCompat.MediaItem} are presented.
|
||||
*
|
||||
* <p>If exposed through {@link MediaBrowserCompat#getExtras()}, the preference applies to all
|
||||
* browsable items within the browse tree.
|
||||
*
|
||||
* <p>If exposed through {@link MediaDescriptionCompat#getExtras()}, the preference applies to
|
||||
* only the immediate browsable children of the corresponding browsable item. It takes precedence
|
||||
* over preferences sent through {@link MediaBrowserCompat#getExtras()}.
|
||||
*
|
||||
* <p>TYPE: int. Possible values are separate constants.
|
||||
*
|
||||
* @see MediaBrowserCompat#getExtras()
|
||||
* @see MediaBrowserServiceCompat.BrowserRoot#BrowserRoot(String, Bundle)
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE =
|
||||
"android.media.browse.CONTENT_STYLE_BROWSABLE_HINT";
|
||||
|
||||
/**
|
||||
* Bundle key sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference about how the corresponding {@link
|
||||
* MediaBrowserCompat.MediaItem} is presented.
|
||||
*
|
||||
* <p>This preference takes precedence over those expressed by {@link
|
||||
* #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE} and {@link
|
||||
* #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE}.
|
||||
*
|
||||
* <p>TYPE: int. Possible values are separate constants.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM =
|
||||
"android.media.browse.CONTENT_STYLE_SINGLE_ITEM_HINT";
|
||||
|
||||
/**
|
||||
* Bundle value passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference that certain instances of {@link
|
||||
* MediaBrowserCompat.MediaItem} should be presented as list items.
|
||||
*
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM = 1;
|
||||
|
||||
/**
|
||||
* Bundle value passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference that certain instances of {@link
|
||||
* MediaBrowserCompat.MediaItem} should be presented as grid items.
|
||||
*
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM = 2;
|
||||
|
||||
/**
|
||||
* Bundle value passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference that browsable instances of {@link
|
||||
* MediaBrowserCompat.MediaItem} should be presented as "category" list items. This means the
|
||||
* items must provide tintable vector drawable icons that render well when they:
|
||||
*
|
||||
* <ul>
|
||||
* <li>do <strong>not</strong> fill all of the available area
|
||||
* <li>are tinted by the system to provide sufficient contrast against the background
|
||||
* </ul>
|
||||
*
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM = 3;
|
||||
|
||||
/**
|
||||
* Bundle value passed from the {@link MediaBrowserServiceCompat} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate a preference that browsable instances of {@link
|
||||
* MediaBrowserCompat.MediaItem} should be presented as "category" grid items. This means the
|
||||
* items must provide tintable vector drawable icons that render well when they:
|
||||
*
|
||||
* <ul>
|
||||
* <li>do <strong>not</strong> fill all of the available area
|
||||
* <li>are tinted by the system to provide sufficient contrast against the background
|
||||
* </ul>
|
||||
*
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM = 4;
|
||||
|
||||
/**
|
||||
* Bundle key sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate that certain instances of {@link MediaBrowserCompat.MediaItem}
|
||||
* are related as a group, with a title that is specified through the bundle value. Items that are
|
||||
* children of the same browsable node and have the same title are members of the same group. The
|
||||
* host may present a group's items as a contiguous block and display the title alongside the
|
||||
* group.
|
||||
*
|
||||
* <p>TYPE: String. Should be human readable and localized.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE =
|
||||
"android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";
|
||||
|
||||
/**
|
||||
* Bundle key sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate the playback completion status of the corresponding {@link
|
||||
* MediaBrowserCompat.MediaItem}.
|
||||
*
|
||||
* <p>TYPE: int. Possible values are separate constants.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
|
||||
* @see #DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS =
|
||||
"android.media.extra.PLAYBACK_STATUS";
|
||||
|
||||
/**
|
||||
* Bundle value sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate that the corresponding {@link MediaBrowserCompat.MediaItem} has
|
||||
* not been played by the user.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED = 0;
|
||||
|
||||
/**
|
||||
* Bundle value sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate that the corresponding {@link MediaBrowserCompat.MediaItem} has
|
||||
* been partially played by the user.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED = 1;
|
||||
|
||||
/**
|
||||
* Bundle value sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate that the corresponding {@link MediaBrowserCompat.MediaItem} has
|
||||
* been fully played by the user.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see #DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
|
||||
*/
|
||||
public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED = 2;
|
||||
|
||||
/**
|
||||
* Bundle key sent through {@link MediaDescriptionCompat#getExtras()} to the hosting {@link
|
||||
* MediaBrowserCompat} to indicate an amount of completion progress for the corresponding {@link
|
||||
* MediaBrowserCompat.MediaItem}. This extra augments {@link
|
||||
* #DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED the partially played status} by
|
||||
* indicating how much has been played by the user.
|
||||
*
|
||||
* <p>TYPE: double, a value between 0.0 and 1.0, inclusive. 0.0 indicates no completion progress
|
||||
* (item is not started) and 1.0 indicates full completion progress (item is fully played). Values
|
||||
* in between indicate partial progress (for example, 0.75 indicates the item is 75% complete).
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE =
|
||||
"androidx.media.MediaItem.Extras.COMPLETION_PERCENTAGE";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to store supported custom browser actions for {@link MediaBrowserCompat
|
||||
* media browsers} that support custom browser actions.
|
||||
*
|
||||
* <p>The browser indicates support for custom browser actions by including the key {@link
|
||||
* MediaConstants#BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT} with a non-zero value in the
|
||||
* root hints bundle passed to {@link MediaBrowserServiceCompat#onGetRoot}.
|
||||
*
|
||||
* <p>Use this key to add an {@link ArrayList} to the {@link Bundle} passed in {@link
|
||||
* MediaBrowserServiceCompat.BrowserRoot}. {@link MediaBrowserServiceCompat} should add this
|
||||
* bundle to the {@link MediaBrowserServiceCompat.BrowserRoot} when {@link
|
||||
* MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)} is called. Use {@link
|
||||
* Bundle#putParcelableArrayList(String, ArrayList)} with a list of bundles, each defining a
|
||||
* custom browser action, to set supported custom browser actions.
|
||||
*
|
||||
* <p>TYPE: arraylist, an ArrayList of {@link Bundle}s, with each bundle defining a browse custom
|
||||
* action.
|
||||
*
|
||||
* <p>A custom browser action is defined by an {@linkplain
|
||||
* MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID action ID}, an {@linkplain
|
||||
* MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL action label}, an {@linkplain
|
||||
* MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI action icon URI}, and optionally an
|
||||
* {@linkplain MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS action extras bundle}.
|
||||
*
|
||||
* <p>Custom browser action example:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Action ID: "com.example.audioapp.download"
|
||||
* <ul>
|
||||
* <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID}
|
||||
* </ul>
|
||||
* <li>Action label: "Download Song"
|
||||
* <ul>
|
||||
* <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL}
|
||||
* <li>Localized String label for action
|
||||
* </ul>
|
||||
* <li>Action Icon URI: "content://com.example.public/download"
|
||||
* <ul>
|
||||
* <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI}
|
||||
* <li>Tintable vector drawable
|
||||
* </ul>
|
||||
* <li>Action extras: {bundle}
|
||||
* <ul>
|
||||
* <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS}
|
||||
* <li>Bundle extras
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST =
|
||||
"androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ROOT_LIST";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define a string list of custom browser actions for a {@link
|
||||
* MediaBrowserCompat.MediaItem}. e.g. "download","favorite","add to queue"
|
||||
*
|
||||
* <p>Supported {@link MediaBrowserCompat media browsers} use this {@link Bundle} key to build a
|
||||
* list of custom browser actions for each {@link MediaBrowserCompat.MediaItem}.
|
||||
*
|
||||
* <p>This key is sent through {@link MediaDescriptionCompat#getExtras()} to the {@link
|
||||
* MediaBrowserCompat} to indicate supported custom browser actions for the corresponding {@link
|
||||
* MediaBrowserCompat.MediaItem}.
|
||||
*
|
||||
* <p>Use {@linkplain Bundle#putStringArrayList(String, ArrayList) a string array list} with a
|
||||
* list of custom browser action IDs. Set this bundle in the {@link MediaBrowserCompat.MediaItem}
|
||||
* using {@link MediaDescriptionCompat.Builder#setExtras(Bundle)} to set the supported browse
|
||||
* custom actions for the {@link MediaBrowserCompat.MediaItem}.
|
||||
*
|
||||
* <p>Each value action in this list must be an action ID defined in {@linkplain
|
||||
* MediaBrowserServiceCompat.BrowserRoot browser root} with {@link Bundle} key {@link
|
||||
* MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST}.
|
||||
*
|
||||
* <p>TYPE: ArrayList<String>, list of String custom browser action IDs.
|
||||
*
|
||||
* @see MediaDescriptionCompat#getExtras()
|
||||
* @see MediaDescriptionCompat.Builder#setExtras(Bundle)
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
*/
|
||||
public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST =
|
||||
"androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ID_LIST";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define the ID for a custom browser action.
|
||||
*
|
||||
* <p>TYPE: String, String ID for a custom browser action.
|
||||
*
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
* @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
|
||||
*/
|
||||
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID =
|
||||
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ID";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define the label for a custom browser action. Label is a localized
|
||||
* string that labels the action for the user.
|
||||
*
|
||||
* <p>TYPE: String, String label for a custom browser action. This must be localized.
|
||||
*
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
* @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
|
||||
*/
|
||||
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL =
|
||||
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_LABEL";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define the icon URI for a custom browser action.
|
||||
*
|
||||
* <p>TYPE: String, String content provider URI for a tintable vector drawable icon.
|
||||
*
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
* @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
|
||||
*/
|
||||
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI =
|
||||
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define an extras bundle for a custom browser action.
|
||||
*
|
||||
* <p>Use {@link Bundle#putBundle(String, Bundle)} on the custom browser action bundle to add this
|
||||
* extras bundle to the custom browser action.
|
||||
*
|
||||
* <p>TYPE: Bundle.
|
||||
*
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
* @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
|
||||
*/
|
||||
public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS =
|
||||
"androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define the total number of actions allowed per item. Passed to
|
||||
* {@link MediaBrowserServiceCompat} using {@link MediaBrowserServiceCompat#onGetRoot(String, int,
|
||||
* Bundle)} in root hints bundle.
|
||||
*
|
||||
* <p>Presence of this key and positive value in the root hints indicates that custom browse
|
||||
* actions feature is supported. Actions beyond this limit will be truncated.
|
||||
*
|
||||
* <p>TYPE: int, number of actions each item is limited to.
|
||||
*
|
||||
* @see MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)
|
||||
* @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
|
||||
* @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
|
||||
*/
|
||||
public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT =
|
||||
"androidx.media.utils.MediaBrowserCompat.extras.CUSTOM_BROWSER_ACTION_LIMIT";
|
||||
|
||||
/**
|
||||
* {@link Bundle} key used to define the ID of the {@link MediaBrowserCompat.MediaItem} associated
|
||||
* with the invoked action.
|
||||
*
|
||||
* <p>A {@link MediaBrowserCompat} that supports custom browser actions can set this key in the
|
||||
* parameter extra bundle when using {@link MediaBrowserCompat#sendCustomAction(String, Bundle,
|
||||
* MediaBrowserCompat.CustomActionCallback)}.
|
||||
*
|
||||
* <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions should override
|
||||
* {@link MediaBrowserServiceCompat#onCustomAction( String, Bundle,
|
||||
* MediaBrowserServiceCompat.Result)} to receive extras bundle set by {@link MediaBrowserCompat
|
||||
* media browsers}.
|
||||
*
|
||||
* <p>TYPE: string, string ID of the {@link MediaBrowserCompat.MediaItem} on which the custom
|
||||
* action was invoked.
|
||||
*
|
||||
* @see MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
|
||||
* @see MediaBrowserCompat#sendCustomAction(String, Bundle,
|
||||
* MediaBrowserCompat.CustomActionCallback)
|
||||
*/
|
||||
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
|
||||
* with {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID media id} of the current metadata.
|
||||
*
|
||||
* <p>TYPE: String
|
||||
*
|
||||
* @see PlaybackStateCompat#getExtras
|
||||
* @see PlaybackStateCompat.Builder#setExtras
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID =
|
||||
"androidx.media.PlaybackStateCompat.Extras.KEY_MEDIA_ID";
|
||||
|
||||
/**
|
||||
* Bundle key passed through {@link PlaybackStateCompat#getExtras()} to the hosting {@link
|
||||
* MediaControllerCompat} which maps to a label. The label is associated with {@link
|
||||
* #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT the action} that allow users to
|
||||
* resolve the current playback state error.
|
||||
*
|
||||
* <p>The label should be short; a more detailed explanation can be provided to the user via
|
||||
* {@link PlaybackStateCompat#getErrorMessage()}.
|
||||
*
|
||||
* <p>TYPE: String. Should be human readable and localized.
|
||||
*
|
||||
* @see PlaybackStateCompat#getExtras()
|
||||
* @see PlaybackStateCompat.Builder#setExtras(Bundle)
|
||||
* @see #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL =
|
||||
"android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
|
||||
|
||||
/**
|
||||
* Bundle key passed through {@link PlaybackStateCompat#getExtras()} to the hosting {@link
|
||||
* MediaControllerCompat} which maps to a pending intent. When launched, the intent should allow
|
||||
* users to resolve the current playback state error. {@link
|
||||
* #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL A label} should be included in the
|
||||
* same Bundle. The key {@link
|
||||
* #BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT} should be
|
||||
* used instead if the intent points to a Car App Library service.
|
||||
*
|
||||
* <p>The intent is NOT auto launched and the user first sees an actionable button with label set
|
||||
* to {@link #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL}. Clicking that button
|
||||
* launches the intent.
|
||||
*
|
||||
* <p>TYPE: PendingIntent. Should be inserted into the Bundle {@link Bundle#putParcelable(String,
|
||||
* Parcelable) as a Parcelable}.
|
||||
*
|
||||
* @see PlaybackStateCompat#getExtras()
|
||||
* @see PlaybackStateCompat.Builder#setExtras(Bundle)
|
||||
* @see #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL
|
||||
* @see #BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT =
|
||||
"android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
|
||||
|
||||
/**
|
||||
* Bundle key passed through {@link PlaybackStateCompat#getExtras()} to the {@link
|
||||
* MediaControllerCompat} which maps to a {@link PendingIntent}. When launched, the {@link
|
||||
* PendingIntent} should allow users to resolve the current playback state error. The intent
|
||||
* should have the component name set to a Car App Library service which exists in the same
|
||||
* application package as the media browser service. The intent may be launched directly unlike
|
||||
* the behavior when using {@link #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT}.
|
||||
*
|
||||
* <p>Applications must also set the error message and {@link
|
||||
* #PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL} for cases in which the intent cannot
|
||||
* be auto launched.
|
||||
*
|
||||
* <p>TYPE: {@link PendingIntent}. Should be inserted into the Bundle {@link
|
||||
* Bundle#putParcelable(String, Parcelable) as a Parcelable}.
|
||||
*
|
||||
* @see PlaybackStateCompat#getExtras()
|
||||
* @see PlaybackStateCompat.Builder#setExtras(Bundle)
|
||||
*/
|
||||
@SuppressLint("IntentName")
|
||||
public static final String
|
||||
PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT =
|
||||
"androidx.media.PlaybackStateCompat.Extras"
|
||||
+ ".ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT";
|
||||
|
||||
/**
|
||||
* 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 the stream type
|
||||
* to be used by the session when playing or preparing the media.
|
||||
*
|
||||
* <p>TYPE: int
|
||||
*
|
||||
* @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_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() {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,657 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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;
|
||||
import android.media.MediaDescription;
|
||||
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 androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RestrictTo;
|
||||
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()}.
|
||||
*/
|
||||
@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
|
||||
* in the section 6.10.2.2 of the Bluetooth AVRCP 1.5. This is valid only for {@link
|
||||
* MediaBrowserCompat.MediaItem} with {@link MediaBrowserCompat.MediaItem#FLAG_BROWSABLE}. The
|
||||
* value should be one of the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #BT_FOLDER_TYPE_MIXED}
|
||||
* <li>{@link #BT_FOLDER_TYPE_TITLES}
|
||||
* <li>{@link #BT_FOLDER_TYPE_ALBUMS}
|
||||
* <li>{@link #BT_FOLDER_TYPE_ARTISTS}
|
||||
* <li>{@link #BT_FOLDER_TYPE_GENRES}
|
||||
* <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}
|
||||
* <li>{@link #BT_FOLDER_TYPE_YEARS}
|
||||
* </ul>
|
||||
*
|
||||
* @see #getExtras()
|
||||
*/
|
||||
public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
|
||||
|
||||
/**
|
||||
* The type of folder that is unknown or contains media elements of mixed types as specified in
|
||||
* the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_MIXED = 0;
|
||||
|
||||
/**
|
||||
* The type of folder that contains media elements only as specified in the section 6.10.2.2 of
|
||||
* the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_TITLES = 1;
|
||||
|
||||
/**
|
||||
* The type of folder that contains folders categorized by album as specified in the section
|
||||
* 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_ALBUMS = 2;
|
||||
|
||||
/**
|
||||
* The type of folder that contains folders categorized by artist as specified in the section
|
||||
* 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_ARTISTS = 3;
|
||||
|
||||
/**
|
||||
* The type of folder that contains folders categorized by genre as specified in the section
|
||||
* 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_GENRES = 4;
|
||||
|
||||
/**
|
||||
* The type of folder that contains folders categorized by playlist as specified in the section
|
||||
* 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
|
||||
|
||||
/**
|
||||
* The type of folder that contains folders categorized by year as specified in the section
|
||||
* 6.10.2.2 of the Bluetooth AVRCP 1.5.
|
||||
*/
|
||||
public static final long BT_FOLDER_TYPE_YEARS = 6;
|
||||
|
||||
/**
|
||||
* Used as a long extra field to indicate the download status of the media item. The value should
|
||||
* be one of the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #STATUS_NOT_DOWNLOADED}
|
||||
* <li>{@link #STATUS_DOWNLOADING}
|
||||
* <li>{@link #STATUS_DOWNLOADED}
|
||||
* </ul>
|
||||
*
|
||||
* @see #getExtras()
|
||||
*/
|
||||
public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
|
||||
|
||||
/**
|
||||
* The status value to indicate the media item is not downloaded.
|
||||
*
|
||||
* @see #EXTRA_DOWNLOAD_STATUS
|
||||
*/
|
||||
public static final long STATUS_NOT_DOWNLOADED = 0;
|
||||
|
||||
/**
|
||||
* The status value to indicate the media item is being downloaded.
|
||||
*
|
||||
* @see #EXTRA_DOWNLOAD_STATUS
|
||||
*/
|
||||
public static final long STATUS_DOWNLOADING = 1;
|
||||
|
||||
/**
|
||||
* The status value to indicate the media item is downloaded for later offline playback.
|
||||
*
|
||||
* @see #EXTRA_DOWNLOAD_STATUS
|
||||
*/
|
||||
public static final long STATUS_DOWNLOADED = 2;
|
||||
|
||||
/**
|
||||
* Custom key to store a media URI on API 21-22 devices (before it became part of the framework
|
||||
* class) when parceling/converting to and from framework objects.
|
||||
*/
|
||||
public static final String DESCRIPTION_KEY_MEDIA_URI =
|
||||
"android.support.v4.media.description.MEDIA_URI";
|
||||
|
||||
/** Custom key to store whether the original Bundle provided by the developer was null */
|
||||
public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG =
|
||||
"android.support.v4.media.description.NULL_BUNDLE_FLAG";
|
||||
|
||||
/** A unique persistent id for the content or null. */
|
||||
@Nullable private final String mMediaId;
|
||||
|
||||
/** A primary title suitable for display or null. */
|
||||
@Nullable private final CharSequence mTitle;
|
||||
|
||||
/** A subtitle suitable for display or null. */
|
||||
@Nullable private final CharSequence mSubtitle;
|
||||
|
||||
/** A description suitable for display or null. */
|
||||
@Nullable private final CharSequence mDescription;
|
||||
|
||||
/** A bitmap icon suitable for display or null. */
|
||||
@Nullable private final Bitmap mIcon;
|
||||
|
||||
/** A Uri for an icon suitable for display or null. */
|
||||
@Nullable private final Uri mIconUri;
|
||||
|
||||
/** Extras for opaque use by apps/system. */
|
||||
@Nullable private final Bundle mExtras;
|
||||
|
||||
/** A Uri to identify this content. */
|
||||
@Nullable private final Uri mMediaUri;
|
||||
|
||||
/** A cached copy of the equivalent framework object. */
|
||||
@Nullable private MediaDescription mDescriptionFwk;
|
||||
|
||||
MediaDescriptionCompat(
|
||||
@Nullable String mediaId,
|
||||
@Nullable CharSequence title,
|
||||
@Nullable CharSequence subtitle,
|
||||
@Nullable CharSequence description,
|
||||
@Nullable Bitmap icon,
|
||||
@Nullable Uri iconUri,
|
||||
@Nullable Bundle extras,
|
||||
@Nullable Uri mediaUri) {
|
||||
mMediaId = mediaId;
|
||||
mTitle = title;
|
||||
mSubtitle = subtitle;
|
||||
mDescription = description;
|
||||
mIcon = icon;
|
||||
mIconUri = iconUri;
|
||||
mExtras = extras;
|
||||
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() {
|
||||
return mMediaId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a title suitable for display or null.
|
||||
*
|
||||
* @return A title or null.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subtitle suitable for display or null.
|
||||
*
|
||||
* @return A subtitle or null.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getSubtitle() {
|
||||
return mSubtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description suitable for display or null.
|
||||
*
|
||||
* @return A description or null.
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmap icon suitable for display or null.
|
||||
*
|
||||
* @return An icon or null.
|
||||
*/
|
||||
@Nullable
|
||||
public Bitmap getIconBitmap() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Uri for an icon suitable for display or null.
|
||||
*
|
||||
* @return An icon uri or null.
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getIconUri() {
|
||||
return mIconUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any extras that were added to the description.
|
||||
*
|
||||
* @return A bundle of extras or null.
|
||||
*/
|
||||
@Nullable
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Uri representing this content or null.
|
||||
*
|
||||
* @return A media Uri or null.
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getMediaUri() {
|
||||
return mMediaUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTitle + ", " + mSubtitle + ", " + mDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public Object 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);
|
||||
// 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
|
||||
// an equivalent MediaDescriptionCompat on all API levels
|
||||
if (Build.VERSION.SDK_INT < 23 && mMediaUri != null) {
|
||||
Bundle extras;
|
||||
if (mExtras == null) {
|
||||
extras = new Bundle();
|
||||
extras.putBoolean(DESCRIPTION_KEY_NULL_BUNDLE_FLAG, true);
|
||||
} else {
|
||||
extras = new Bundle(mExtras);
|
||||
}
|
||||
extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
|
||||
Api21Impl.setExtras(bob, extras);
|
||||
} else {
|
||||
Api21Impl.setExtras(bob, mExtras);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
Api23Impl.setMediaUri(bob, mMediaUri);
|
||||
}
|
||||
mDescriptionFwk = Api21Impl.build(bob);
|
||||
|
||||
return mDescriptionFwk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaDescriptionCompat[] newArray(int size) {
|
||||
return new MediaDescriptionCompat[size];
|
||||
}
|
||||
};
|
||||
|
||||
/** Builder for {@link MediaDescriptionCompat} objects. */
|
||||
public static final class Builder {
|
||||
@Nullable private String mMediaId;
|
||||
@Nullable private CharSequence mTitle;
|
||||
@Nullable private CharSequence mSubtitle;
|
||||
@Nullable private CharSequence mDescription;
|
||||
@Nullable private Bitmap mIcon;
|
||||
@Nullable private Uri mIconUri;
|
||||
@Nullable private Bundle mExtras;
|
||||
@Nullable private Uri mMediaUri;
|
||||
|
||||
/** Creates an initially empty builder. */
|
||||
public Builder() {}
|
||||
|
||||
/**
|
||||
* Sets the media id.
|
||||
*
|
||||
* @param mediaId The unique id for the item or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setMediaId(@Nullable String mediaId) {
|
||||
mMediaId = mediaId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title.
|
||||
*
|
||||
* @param title A title suitable for display to the user or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setTitle(@Nullable CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle.
|
||||
*
|
||||
* @param subtitle A subtitle suitable for display to the user or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setSubtitle(@Nullable CharSequence subtitle) {
|
||||
mSubtitle = subtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description.
|
||||
*
|
||||
* @param description A description suitable for display to the user or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setDescription(@Nullable CharSequence description) {
|
||||
mDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon.
|
||||
*
|
||||
* @param icon A {@link Bitmap} icon suitable for display to the user or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setIconBitmap(@Nullable Bitmap icon) {
|
||||
mIcon = icon;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon uri.
|
||||
*
|
||||
* @param iconUri A {@link Uri} for an icon suitable for display to the user or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setIconUri(@Nullable Uri iconUri) {
|
||||
mIconUri = iconUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bundle of extras.
|
||||
*
|
||||
* @param extras The extras to include with this description or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setExtras(@Nullable Bundle extras) {
|
||||
mExtras = extras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media uri.
|
||||
*
|
||||
* @param mediaUri The content's {@link Uri} for the item or null.
|
||||
* @return this
|
||||
*/
|
||||
public Builder setMediaUri(@Nullable Uri mediaUri) {
|
||||
mMediaUri = mediaUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MediaDescriptionCompat} instance with the specified fields.
|
||||
*
|
||||
* @return A MediaDescriptionCompat instance.
|
||||
*/
|
||||
public MediaDescriptionCompat build() {
|
||||
return new MediaDescriptionCompat(
|
||||
mMediaId, mTitle, mSubtitle, mDescription, mIcon, mIconUri, mExtras, mMediaUri);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static class Api21Impl {
|
||||
private Api21Impl() {}
|
||||
|
||||
@DoNotInline
|
||||
static MediaDescription.Builder createBuilder() {
|
||||
return new MediaDescription.Builder();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setMediaId(MediaDescription.Builder builder, @Nullable String mediaId) {
|
||||
builder.setMediaId(mediaId);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setTitle(MediaDescription.Builder builder, @Nullable CharSequence title) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setSubtitle(MediaDescription.Builder builder, @Nullable CharSequence subtitle) {
|
||||
builder.setSubtitle(subtitle);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setDescription(
|
||||
MediaDescription.Builder builder, @Nullable CharSequence description) {
|
||||
builder.setDescription(description);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setIconBitmap(MediaDescription.Builder builder, @Nullable Bitmap icon) {
|
||||
builder.setIconBitmap(icon);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setIconUri(MediaDescription.Builder builder, @Nullable Uri iconUri) {
|
||||
builder.setIconUri(iconUri);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static void setExtras(MediaDescription.Builder builder, @Nullable Bundle extras) {
|
||||
builder.setExtras(extras);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
static MediaDescription build(MediaDescription.Builder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static String getMediaId(MediaDescription description) {
|
||||
return description.getMediaId();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static CharSequence getTitle(MediaDescription description) {
|
||||
return description.getTitle();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static CharSequence getSubtitle(MediaDescription description) {
|
||||
return description.getSubtitle();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static CharSequence getDescription(MediaDescription description) {
|
||||
return description.getDescription();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static Bitmap getIconBitmap(MediaDescription description) {
|
||||
return description.getIconBitmap();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static Uri getIconUri(MediaDescription description) {
|
||||
return description.getIconUri();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static Bundle getExtras(MediaDescription description) {
|
||||
return description.getExtras();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private static class Api23Impl {
|
||||
private Api23Impl() {}
|
||||
|
||||
@DoNotInline
|
||||
static void setMediaUri(MediaDescription.Builder builder, @Nullable Uri mediaUri) {
|
||||
builder.setMediaUri(mediaUri);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
@Nullable
|
||||
static Uri getMediaUri(MediaDescription description) {
|
||||
return description.getMediaUri();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,833 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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;
|
||||
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
|
||||
@RestrictTo(LIBRARY)
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
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";
|
||||
|
||||
/** The artist of the media. */
|
||||
public static final String METADATA_KEY_ARTIST = "android.media.metadata.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";
|
||||
|
||||
/** The album title for the media. */
|
||||
public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
|
||||
|
||||
/** The author of the media. */
|
||||
public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
|
||||
|
||||
/** The writer of the media. */
|
||||
public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
|
||||
|
||||
/** The composer of the media. */
|
||||
public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
|
||||
|
||||
/** The compilation status of the media. */
|
||||
public static final String METADATA_KEY_COMPILATION = "android.media.metadata.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";
|
||||
|
||||
/** The year the media was created or published as a long. */
|
||||
public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
|
||||
|
||||
/** The genre of the media. */
|
||||
public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
|
||||
|
||||
/** The track number for the media. */
|
||||
public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.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";
|
||||
|
||||
/** The disc number for the media's original source. */
|
||||
public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.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";
|
||||
|
||||
/**
|
||||
* The artwork for the media as a {@link Bitmap}.
|
||||
*
|
||||
* <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";
|
||||
|
||||
/** The artwork for the media as a Uri style String. */
|
||||
public static final String METADATA_KEY_ART_URI = "android.media.metadata.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";
|
||||
|
||||
/** 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";
|
||||
|
||||
/**
|
||||
* The user's rating for the media.
|
||||
*
|
||||
* @see RatingCompat
|
||||
*/
|
||||
public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
|
||||
|
||||
/**
|
||||
* The overall rating for the media.
|
||||
*
|
||||
* @see RatingCompat
|
||||
*/
|
||||
public static final String METADATA_KEY_RATING = "android.media.metadata.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";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* An icon or thumbnail that is suitable for display to the user. When displaying an icon for
|
||||
* media described by this metadata this should be preferred to other fields if present. This must
|
||||
* be a {@link Bitmap}.
|
||||
*
|
||||
* <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";
|
||||
|
||||
/**
|
||||
* An icon or thumbnail that is suitable for display to the user. When displaying more information
|
||||
* for media described by this metadata the display description should be preferred to other
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth AVRCP
|
||||
* 1.5. It should be one of the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_MIXED}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_TITLES}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_ALBUMS}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_ARTISTS}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_GENRES}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_PLAYLISTS}
|
||||
* <li>{@link MediaDescriptionCompat#BT_FOLDER_TYPE_YEARS}
|
||||
* </ul>
|
||||
*/
|
||||
public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
|
||||
|
||||
/**
|
||||
* Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A
|
||||
* value of 1 or non-zero indicates it is an advertisement. If not specified, this value is set to
|
||||
* 0 by default.
|
||||
*/
|
||||
public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
|
||||
|
||||
/**
|
||||
* The download status of the media which will be used for later offline playback. It should be
|
||||
* one of the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link MediaDescriptionCompat#STATUS_NOT_DOWNLOADED}
|
||||
* <li>{@link MediaDescriptionCompat#STATUS_DOWNLOADING}
|
||||
* <li>{@link MediaDescriptionCompat#STATUS_DOWNLOADED}
|
||||
* </ul>
|
||||
*/
|
||||
public static final String METADATA_KEY_DOWNLOAD_STATUS =
|
||||
"android.media.metadata.DOWNLOAD_STATUS";
|
||||
|
||||
@StringDef(
|
||||
value = {
|
||||
METADATA_KEY_TITLE,
|
||||
METADATA_KEY_ARTIST,
|
||||
METADATA_KEY_ALBUM,
|
||||
METADATA_KEY_AUTHOR,
|
||||
METADATA_KEY_WRITER,
|
||||
METADATA_KEY_COMPOSER,
|
||||
METADATA_KEY_COMPILATION,
|
||||
METADATA_KEY_DATE,
|
||||
METADATA_KEY_GENRE,
|
||||
METADATA_KEY_ALBUM_ARTIST,
|
||||
METADATA_KEY_ART_URI,
|
||||
METADATA_KEY_ALBUM_ART_URI,
|
||||
METADATA_KEY_DISPLAY_TITLE,
|
||||
METADATA_KEY_DISPLAY_SUBTITLE,
|
||||
METADATA_KEY_DISPLAY_DESCRIPTION,
|
||||
METADATA_KEY_DISPLAY_ICON_URI,
|
||||
METADATA_KEY_MEDIA_ID,
|
||||
METADATA_KEY_MEDIA_URI
|
||||
},
|
||||
open = true)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface TextKey {}
|
||||
|
||||
@StringDef(
|
||||
value = {
|
||||
METADATA_KEY_DURATION,
|
||||
METADATA_KEY_YEAR,
|
||||
METADATA_KEY_TRACK_NUMBER,
|
||||
METADATA_KEY_NUM_TRACKS,
|
||||
METADATA_KEY_DISC_NUMBER,
|
||||
METADATA_KEY_BT_FOLDER_TYPE,
|
||||
METADATA_KEY_ADVERTISEMENT,
|
||||
METADATA_KEY_DOWNLOAD_STATUS
|
||||
},
|
||||
open = true)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface LongKey {}
|
||||
|
||||
@StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface BitmapKey {}
|
||||
|
||||
@StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface RatingKey {}
|
||||
|
||||
static final int METADATA_TYPE_LONG = 0;
|
||||
static final int METADATA_TYPE_TEXT = 1;
|
||||
static final int METADATA_TYPE_BITMAP = 2;
|
||||
static final int METADATA_TYPE_RATING = 3;
|
||||
static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
|
||||
|
||||
static {
|
||||
METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
|
||||
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);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
|
||||
METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
|
||||
}
|
||||
|
||||
private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
|
||||
METADATA_KEY_TITLE,
|
||||
METADATA_KEY_ARTIST,
|
||||
METADATA_KEY_ALBUM,
|
||||
METADATA_KEY_ALBUM_ARTIST,
|
||||
METADATA_KEY_WRITER,
|
||||
METADATA_KEY_AUTHOR,
|
||||
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);
|
||||
MediaSessionCompat.ensureClassLoader(mBundle);
|
||||
}
|
||||
|
||||
MediaMetadataCompat(Parcel in) {
|
||||
mBundle = checkNotNull(in.readBundle(MediaSessionCompat.class.getClassLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given key is contained in the metadata
|
||||
*
|
||||
* @param key a String key
|
||||
* @return true if the key exists in this metadata, false otherwise
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return mBundle.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key, or null if no mapping of the desired type
|
||||
* exists for the given key or a null value is explicitly associated with the key.
|
||||
*
|
||||
* @param key The key the value is stored under
|
||||
* @return a CharSequence value, or null
|
||||
*/
|
||||
@Nullable
|
||||
public CharSequence getText(@TextKey String key) {
|
||||
return mBundle.getCharSequence(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key, or null if no mapping of the desired type
|
||||
* exists for the given key or a null value is explicitly associated with the key.
|
||||
*
|
||||
* @param key The key the value is stored under
|
||||
* @return a String value, or null
|
||||
*/
|
||||
@Nullable
|
||||
public String getString(@TextKey String key) {
|
||||
CharSequence text = mBundle.getCharSequence(key);
|
||||
if (text != null) {
|
||||
return text.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key, or 0L if no long exists for the given key.
|
||||
*
|
||||
* @param key The key the value is stored under
|
||||
* @return a long value
|
||||
*/
|
||||
public long getLong(@LongKey String key) {
|
||||
return mBundle.getLong(key, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link RatingCompat} for the given key or null if no rating exists for the given key.
|
||||
*
|
||||
* @param key The key the value is stored under
|
||||
* @return A {@link RatingCompat} or null
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
public RatingCompat getRating(@RatingKey String key) {
|
||||
RatingCompat rating = null;
|
||||
try {
|
||||
// On platform version 19 or higher, mBundle stores a Rating object. Convert it to
|
||||
// RatingCompat.
|
||||
rating = RatingCompat.fromRating(mBundle.getParcelable(key));
|
||||
} catch (Exception e) {
|
||||
// ignore, value was not a bitmap
|
||||
Log.w(TAG, "Failed to retrieve a key as Rating.", e);
|
||||
}
|
||||
return rating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Bitmap} for the given key or null if no bitmap exists for the given key.
|
||||
*
|
||||
* @param key The key the value is stored under
|
||||
* @return A {@link Bitmap} or null
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
public Bitmap getBitmap(@BitmapKey String key) {
|
||||
Bitmap bmp = null;
|
||||
try {
|
||||
bmp = mBundle.getParcelable(key);
|
||||
} catch (Exception e) {
|
||||
// ignore, value was not a bitmap
|
||||
Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeBundle(mBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of fields in this metadata.
|
||||
*
|
||||
* @return The number of fields in the metadata.
|
||||
*/
|
||||
public int size() {
|
||||
return mBundle.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Set containing the Strings used as keys in this metadata.
|
||||
*
|
||||
* @return a Set of String keys
|
||||
*/
|
||||
public Set<String> keySet() {
|
||||
return mBundle.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a copy of the bundle for this metadata object. This is available to support backwards
|
||||
* compatibility.
|
||||
*
|
||||
* @return A copy of the bundle for this metadata object.
|
||||
*/
|
||||
public Bundle getBundle() {
|
||||
return new Bundle(mBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from a framework {@link android.media.MediaMetadata} object.
|
||||
*
|
||||
* <p>This method is only supported on {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
|
||||
*
|
||||
* @param metadataObj A {@link android.media.MediaMetadata} object, or null if none.
|
||||
* @return An equivalent {@link MediaMetadataCompat} object, or null if none.
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaMetadataCompat fromMediaMetadata(@Nullable Object metadataObj) {
|
||||
if (metadataObj != null && Build.VERSION.SDK_INT >= 21) {
|
||||
Parcel p = Parcel.obtain();
|
||||
((MediaMetadata) metadataObj).writeToParcel(p, 0);
|
||||
p.setDataPosition(0);
|
||||
MediaMetadataCompat metadata = MediaMetadataCompat.CREATOR.createFromParcel(p);
|
||||
p.recycle();
|
||||
metadata.mMetadataFwk = (MediaMetadata) metadataObj;
|
||||
return metadata;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public Object getMediaMetadata() {
|
||||
if (mMetadataFwk == null) {
|
||||
Parcel p = Parcel.obtain();
|
||||
try {
|
||||
writeToParcel(p, 0);
|
||||
p.setDataPosition(0);
|
||||
mMetadataFwk = MediaMetadata.CREATOR.createFromParcel(p);
|
||||
return mMetadataFwk;
|
||||
} finally {
|
||||
p.recycle();
|
||||
}
|
||||
}
|
||||
return mMetadataFwk;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MediaMetadataCompat> CREATOR =
|
||||
new Parcelable.Creator<MediaMetadataCompat>() {
|
||||
@Override
|
||||
public MediaMetadataCompat createFromParcel(Parcel in) {
|
||||
return new MediaMetadataCompat(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaMetadataCompat[] newArray(int size) {
|
||||
return new MediaMetadataCompat[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to build MediaMetadata objects. The system defined metadata keys must use the appropriate
|
||||
* data type.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final Bundle mBundle;
|
||||
|
||||
/**
|
||||
* Create an empty Builder. Any field that should be included in the {@link MediaMetadataCompat}
|
||||
* must be added.
|
||||
*/
|
||||
public Builder() {
|
||||
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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #METADATA_KEY_TITLE}
|
||||
* <li>{@link #METADATA_KEY_ARTIST}
|
||||
* <li>{@link #METADATA_KEY_ALBUM}
|
||||
* <li>{@link #METADATA_KEY_AUTHOR}
|
||||
* <li>{@link #METADATA_KEY_WRITER}
|
||||
* <li>{@link #METADATA_KEY_COMPOSER}
|
||||
* <li>{@link #METADATA_KEY_DATE}
|
||||
* <li>{@link #METADATA_KEY_GENRE}
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}
|
||||
* <li>{@link #METADATA_KEY_ART_URI}
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_TITLE}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}
|
||||
* </ul>
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
* @param value The CharSequence value to store
|
||||
* @return The Builder to allow chaining
|
||||
*/
|
||||
public Builder putText(@TextKey String key, CharSequence value) {
|
||||
Integer type = METADATA_KEYS_TYPE.get(key);
|
||||
if (type != null && type != METADATA_TYPE_TEXT) {
|
||||
throw new IllegalArgumentException(
|
||||
"The " + key + " key cannot be used to put a CharSequence");
|
||||
}
|
||||
mBundle.putCharSequence(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a String 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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #METADATA_KEY_TITLE}
|
||||
* <li>{@link #METADATA_KEY_ARTIST}
|
||||
* <li>{@link #METADATA_KEY_ALBUM}
|
||||
* <li>{@link #METADATA_KEY_AUTHOR}
|
||||
* <li>{@link #METADATA_KEY_WRITER}
|
||||
* <li>{@link #METADATA_KEY_COMPOSER}
|
||||
* <li>{@link #METADATA_KEY_DATE}
|
||||
* <li>{@link #METADATA_KEY_GENRE}
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}
|
||||
* <li>{@link #METADATA_KEY_ART_URI}
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_TITLE}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}
|
||||
* </ul>
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
* @param value The String value to store
|
||||
* @return The Builder to allow chaining
|
||||
*/
|
||||
public Builder putString(@TextKey String key, String value) {
|
||||
Integer type = METADATA_KEYS_TYPE.get(key);
|
||||
if (type != null && type != METADATA_TYPE_TEXT) {
|
||||
throw new IllegalArgumentException("The " + key + " key cannot be used to put a String");
|
||||
}
|
||||
mBundle.putCharSequence(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a long 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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #METADATA_KEY_DURATION}
|
||||
* <li>{@link #METADATA_KEY_TRACK_NUMBER}
|
||||
* <li>{@link #METADATA_KEY_NUM_TRACKS}
|
||||
* <li>{@link #METADATA_KEY_DISC_NUMBER}
|
||||
* <li>{@link #METADATA_KEY_YEAR}
|
||||
* <li>{@link #METADATA_KEY_BT_FOLDER_TYPE}
|
||||
* <li>{@link #METADATA_KEY_ADVERTISEMENT}
|
||||
* <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}
|
||||
* </ul>
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
* @param value The String value to store
|
||||
* @return The Builder to allow chaining
|
||||
*/
|
||||
public Builder putLong(@LongKey String key, long value) {
|
||||
Integer type = METADATA_KEYS_TYPE.get(key);
|
||||
if (type != null && type != METADATA_TYPE_LONG) {
|
||||
throw new IllegalArgumentException("The " + key + " key cannot be used to put a long");
|
||||
}
|
||||
mBundle.putLong(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a {@link RatingCompat} 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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #METADATA_KEY_RATING}
|
||||
* <li>{@link #METADATA_KEY_USER_RATING}
|
||||
* </ul>
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
* @param value The String value to store
|
||||
* @return The Builder to allow chaining
|
||||
*/
|
||||
public Builder putRating(@RatingKey String key, RatingCompat value) {
|
||||
Integer type = METADATA_KEYS_TYPE.get(key);
|
||||
if (type != null && type != METADATA_TYPE_RATING) {
|
||||
throw new IllegalArgumentException("The " + key + " key cannot be used to put a Rating");
|
||||
}
|
||||
// On platform version 19 or higher, use Rating instead of RatingCompat so mBundle
|
||||
// can be unmarshalled.
|
||||
mBundle.putParcelable(key, (Parcelable) value.getRating());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a {@link Bitmap} 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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #METADATA_KEY_ART}
|
||||
* <li>{@link #METADATA_KEY_ALBUM_ART}
|
||||
* <li>{@link #METADATA_KEY_DISPLAY_ICON}
|
||||
* </ul>
|
||||
*
|
||||
* Large bitmaps may be scaled down when {@link
|
||||
* androidx.media3.session.legacy.MediaSessionCompat#setMetadata} is called. To pass full
|
||||
* resolution images {@link Uri Uris} should be used with {@link #putString}.
|
||||
*
|
||||
* @param key The key for referencing this value
|
||||
* @param value The Bitmap to store
|
||||
* @return The Builder to allow chaining
|
||||
*/
|
||||
public Builder putBitmap(@BitmapKey String key, Bitmap value) {
|
||||
Integer type = METADATA_KEYS_TYPE.get(key);
|
||||
if (type != null && type != METADATA_TYPE_BITMAP) {
|
||||
throw new IllegalArgumentException("The " + key + " key cannot be used to put a Bitmap");
|
||||
}
|
||||
mBundle.putParcelable(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MediaMetadataCompat} instance with the specified fields.
|
||||
*
|
||||
* @return The new MediaMetadata instance
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/**
|
||||
* Provides support for interacting with {@link MediaSessionCompat media sessions} that applications
|
||||
* have published to express their ongoing media playback state.
|
||||
*
|
||||
* @see MediaSessionCompat
|
||||
* @see MediaControllerCompat
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public final class MediaSessionManager {
|
||||
static final String TAG = "MediaSessionManager";
|
||||
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
@Nullable private static volatile MediaSessionManager sSessionManager;
|
||||
|
||||
MediaSessionManagerImpl mImpl;
|
||||
|
||||
/**
|
||||
* Gets an instance of the media session manager associated with the context.
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
return sSessionManager;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the remote user is a trusted app.
|
||||
*
|
||||
* <p>An app is trusted if the app holds the android.Manifest.permission.MEDIA_CONTENT_CONTROL
|
||||
* permission or has an enabled notification listener.
|
||||
*
|
||||
* @param userInfo The remote user info from either {@link
|
||||
* MediaSessionCompat#getCurrentControllerInfo()} and {@link
|
||||
* MediaBrowserServiceCompat#getCurrentBrowserInfo()}.
|
||||
* @return {@code true} if the remote user is trusted and its package name matches with the UID.
|
||||
* {@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();
|
||||
|
||||
int getPid();
|
||||
|
||||
int getUid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information of a remote user of {@link MediaSessionCompat} or {@link
|
||||
* MediaBrowserServiceCompat}. This can be used to decide whether the remote user is trusted app,
|
||||
* and also differentiate caller of {@link MediaSessionCompat} and {@link
|
||||
* MediaBrowserServiceCompat} callbacks.
|
||||
*
|
||||
* <p>See {@link #equals(Object)} to take a look at how it differentiate media controller.
|
||||
*
|
||||
* @see #isTrustedForMediaControl(RemoteUserInfo)
|
||||
*/
|
||||
public static final class RemoteUserInfo {
|
||||
/**
|
||||
* Used by {@link #getPackageName()} when the session is connected to the legacy controller
|
||||
* whose exact package name cannot be obtained.
|
||||
*/
|
||||
public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
|
||||
|
||||
/** Represents an unknown pid of an application. */
|
||||
public static final int UNKNOWN_PID = -1;
|
||||
|
||||
/** Represents an unknown uid of an application. */
|
||||
public static final int UNKNOWN_UID = -1;
|
||||
|
||||
RemoteUserInfoImpl mImpl;
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
*
|
||||
* <p>Can be used for {@link MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)}}.
|
||||
*
|
||||
* @param packageName package name of the remote user
|
||||
* @param pid pid of the remote user
|
||||
* @param uid uid of the remote user
|
||||
* @throws IllegalArgumentException if package name is empty
|
||||
*/
|
||||
public RemoteUserInfo(@Nullable String packageName, int pid, int uid) {
|
||||
if (packageName == null) {
|
||||
throw new NullPointerException("package shouldn't be null");
|
||||
} else if (TextUtils.isEmpty(packageName)) {
|
||||
throw new IllegalArgumentException("packageName should be nonempty");
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
mImpl = new MediaSessionManagerImplApi28.RemoteUserInfoImplApi28(packageName, pid, uid);
|
||||
} else {
|
||||
// Note: We need to include IBinder to distinguish controllers in a process.
|
||||
mImpl = new MediaSessionManagerImplBase.RemoteUserInfoImplBase(packageName, pid, uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor for internal uses.
|
||||
*
|
||||
* <p>Internal code MUST use this on SDK >= 28 to distinguish individual RemoteUserInfos in a
|
||||
* process.
|
||||
*
|
||||
* @param remoteUserInfo Framework RemoteUserInfo
|
||||
* @throws IllegalArgumentException if package name is empty
|
||||
*/
|
||||
@RequiresApi(28)
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return package name of the controller. Can be {@link #LEGACY_CONTROLLER} if the package name
|
||||
* cannot be obtained.
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return mImpl.getPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return pid of the controller. Can be a negative value if the pid cannot be obtained.
|
||||
*/
|
||||
public int getPid() {
|
||||
return mImpl.getPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return uid of the controller. Can be a negative value if the uid cannot be obtained.
|
||||
*/
|
||||
public int getUid() {
|
||||
return mImpl.getUid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns equality of two RemoteUserInfo by comparing their package name, UID, and PID.
|
||||
*
|
||||
* <p>On P and before (API ≤ 28), two RemoteUserInfo objects equal if following conditions
|
||||
* are met:
|
||||
*
|
||||
* <ol>
|
||||
* <li>UID and package name are the same
|
||||
* <li>One of the RemoteUserInfo's PID is UNKNOWN_PID or both of RemoteUserInfo's PID are the
|
||||
* same
|
||||
* </ol>
|
||||
*
|
||||
* @param obj the reference object with which to compare.
|
||||
* @return {@code true} if equals, {@code false} otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof RemoteUserInfo)) {
|
||||
return false;
|
||||
}
|
||||
return mImpl.equals(((RemoteUserInfo) obj).mImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mImpl.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static class MediaSessionManagerImplBase
|
||||
implements MediaSessionManager.MediaSessionManagerImpl {
|
||||
private static final String TAG = MediaSessionManager.TAG;
|
||||
private static final boolean DEBUG = MediaSessionManager.DEBUG;
|
||||
|
||||
private static final String PERMISSION_STATUS_BAR_SERVICE =
|
||||
"android.permission.STATUS_BAR_SERVICE";
|
||||
private static final String PERMISSION_MEDIA_CONTENT_CONTROL =
|
||||
"android.permission.MEDIA_CONTENT_CONTROL";
|
||||
private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
|
||||
|
||||
Context mContext;
|
||||
ContentResolver mContentResolver;
|
||||
|
||||
MediaSessionManagerImplBase(Context context) {
|
||||
mContext = context;
|
||||
mContentResolver = mContext.getContentResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
|
||||
try {
|
||||
ApplicationInfo applicationInfo =
|
||||
mContext.getPackageManager().getApplicationInfo(userInfo.getPackageName(), 0);
|
||||
if (applicationInfo == null) {
|
||||
return false;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Package " + userInfo.getPackageName() + " doesn't exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return isPermissionGranted(userInfo, PERMISSION_STATUS_BAR_SERVICE)
|
||||
|| isPermissionGranted(userInfo, PERMISSION_MEDIA_CONTENT_CONTROL)
|
||||
|| userInfo.getUid() == Process.SYSTEM_UID
|
||||
|| isEnabledNotificationListener(userInfo);
|
||||
}
|
||||
|
||||
private boolean isPermissionGranted(
|
||||
MediaSessionManager.RemoteUserInfoImpl userInfo, String permission) {
|
||||
if (userInfo.getPid() < 0) {
|
||||
// This may happen for the MediaBrowserServiceCompat#onGetRoot().
|
||||
return mContext.getPackageManager().checkPermission(permission, userInfo.getPackageName())
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return mContext.checkPermission(permission, userInfo.getPid(), userInfo.getUid())
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the component is an enabled notification listener for the specified user.
|
||||
* Enabled components may only operate on behalf of the user they're running as.
|
||||
*
|
||||
* @return True if the component is enabled, false otherwise
|
||||
*/
|
||||
@SuppressWarnings("StringSplitter")
|
||||
boolean isEnabledNotificationListener(MediaSessionManager.RemoteUserInfoImpl userInfo) {
|
||||
final String enabledNotifListeners =
|
||||
Settings.Secure.getString(mContentResolver, ENABLED_NOTIFICATION_LISTENERS);
|
||||
if (enabledNotifListeners != null) {
|
||||
final String[] components = enabledNotifListeners.split(":");
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
final ComponentName component = ComponentName.unflattenFromString(components[i]);
|
||||
if (component != null) {
|
||||
if (component.getPackageName().equals(userInfo.getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static class RemoteUserInfoImplBase implements MediaSessionManager.RemoteUserInfoImpl {
|
||||
private String mPackageName;
|
||||
private int mPid;
|
||||
private int mUid;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
|
||||
|
||||
return hasMediaControlPermission(userInfo) || super.isTrustedForMediaControl(userInfo);
|
||||
}
|
||||
|
||||
/** Checks the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission. */
|
||||
private boolean hasMediaControlPermission(MediaSessionManager.RemoteUserInfoImpl userInfo) {
|
||||
return getContext()
|
||||
.checkPermission(
|
||||
android.Manifest.permission.MEDIA_CONTENT_CONTROL,
|
||||
userInfo.getPid(),
|
||||
userInfo.getUid())
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(28)
|
||||
private static final class MediaSessionManagerImplApi28 extends MediaSessionManagerImplApi21 {
|
||||
@Nullable android.media.session.MediaSessionManager mObject;
|
||||
|
||||
MediaSessionManagerImplApi28(Context context) {
|
||||
super(context);
|
||||
mObject =
|
||||
(android.media.session.MediaSessionManager)
|
||||
context.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedForMediaControl(MediaSessionManager.RemoteUserInfoImpl userInfo) {
|
||||
// Don't use framework's isTrustedForMediaControl().
|
||||
// In P, framework's isTrustedForMediaControl() checks whether the UID, PID,
|
||||
// and package name match. In MediaSession/MediaController, Context#getPackageName() is
|
||||
// used by MediaController to tell MediaSession the package name.
|
||||
// However, UID, PID and Context#getPackageName() may not match if a activity/service runs
|
||||
// on the another app's process by specifying android:process in the AndroidManifest.xml.
|
||||
// In that case, this check will always fail.
|
||||
// Alternative way is to use Context#getOpPackageName() for sending the package name,
|
||||
// but it's hidden so we cannot use it.
|
||||
return super.isTrustedForMediaControl(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* This extends {@link RemoteUserInfoImplBase} on purpose not to use frameworks' equals() and
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/**
|
||||
* Convenience class for passing information about the audio configuration of a {@link
|
||||
* MediaSessionCompat}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
public class ParcelableVolumeInfo implements Parcelable {
|
||||
public int volumeType;
|
||||
public int audioStream;
|
||||
public int controlType;
|
||||
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();
|
||||
maxVolume = from.readInt();
|
||||
currentVolume = from.readInt();
|
||||
audioStream = from.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(volumeType);
|
||||
dest.writeInt(controlType);
|
||||
dest.writeInt(maxVolume);
|
||||
dest.writeInt(currentVolume);
|
||||
dest.writeInt(audioStream);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR =
|
||||
new Parcelable.Creator<ParcelableVolumeInfo>() {
|
||||
@Override
|
||||
public ParcelableVolumeInfo createFromParcel(Parcel in) {
|
||||
return new ParcelableVolumeInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelableVolumeInfo[] newArray(int size) {
|
||||
return new ParcelableVolumeInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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.media.Rating;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A class to encapsulate rating information used as content metadata. A rating is defined by its
|
||||
* rating style (see {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS},
|
||||
* {@link #RATING_4_STARS}, {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual
|
||||
* rating value (which may be defined as "unrated"), both of which are defined when the rating
|
||||
* instance is constructed through one of the factory methods.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
@SuppressLint("BanParcelableUsage")
|
||||
public final class RatingCompat implements Parcelable {
|
||||
private static final String TAG = "Rating";
|
||||
|
||||
/** */
|
||||
@IntDef({
|
||||
RATING_NONE,
|
||||
RATING_HEART,
|
||||
RATING_THUMB_UP_DOWN,
|
||||
RATING_3_STARS,
|
||||
RATING_4_STARS,
|
||||
RATING_5_STARS,
|
||||
RATING_PERCENTAGE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Style {}
|
||||
|
||||
/** */
|
||||
@IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface StarStyle {}
|
||||
|
||||
/**
|
||||
* Indicates a rating style is not supported. A Rating will never have this type, but can be used
|
||||
* by other classes to indicate they do not support Rating.
|
||||
*/
|
||||
public static final int RATING_NONE = 0;
|
||||
|
||||
/**
|
||||
* A rating style with a single degree of rating, "heart" vs "no heart". Can be used to indicate
|
||||
* the content referred to is a favorite (or not).
|
||||
*/
|
||||
public static final int RATING_HEART = 1;
|
||||
|
||||
/** A rating style for "thumb up" vs "thumb down". */
|
||||
public static final int RATING_THUMB_UP_DOWN = 2;
|
||||
|
||||
/** A rating style with 0 to 3 stars. */
|
||||
public static final int RATING_3_STARS = 3;
|
||||
|
||||
/** A rating style with 0 to 4 stars. */
|
||||
public static final int RATING_4_STARS = 4;
|
||||
|
||||
/** A rating style with 0 to 5 stars. */
|
||||
public static final int RATING_5_STARS = 5;
|
||||
|
||||
/** A rating style expressed as a percentage. */
|
||||
public static final int RATING_PERCENTAGE = 6;
|
||||
|
||||
private static final float RATING_NOT_RATED = -1.0f;
|
||||
|
||||
private final int mRatingStyle;
|
||||
private final float mRatingValue;
|
||||
|
||||
@Nullable private Object mRatingObj; // framework Rating object
|
||||
|
||||
RatingCompat(@Style int ratingStyle, float rating) {
|
||||
mRatingStyle = ratingStyle;
|
||||
mRatingValue = rating;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rating:style="
|
||||
+ mRatingStyle
|
||||
+ " rating="
|
||||
+ (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return mRatingStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mRatingStyle);
|
||||
dest.writeFloat(mRatingValue);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RatingCompat> CREATOR =
|
||||
new Parcelable.Creator<RatingCompat>() {
|
||||
/**
|
||||
* Rebuilds a Rating previously stored with writeToParcel().
|
||||
*
|
||||
* @param p Parcel object to read the Rating from
|
||||
* @return a new Rating created from the data in the parcel
|
||||
*/
|
||||
@Override
|
||||
public RatingCompat createFromParcel(Parcel p) {
|
||||
return new RatingCompat(p.readInt(), p.readFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RatingCompat[] newArray(int size) {
|
||||
return new RatingCompat[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Rating instance with no rating. Create and return a new Rating instance with no rating
|
||||
* known for the given rating style.
|
||||
*
|
||||
* @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, {@link
|
||||
* #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, or {@link
|
||||
* #RATING_PERCENTAGE}.
|
||||
* @return null if an invalid rating style is passed, a new Rating instance otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public static RatingCompat newUnratedRating(@Style int ratingStyle) {
|
||||
switch (ratingStyle) {
|
||||
case RATING_HEART:
|
||||
case RATING_THUMB_UP_DOWN:
|
||||
case RATING_3_STARS:
|
||||
case RATING_4_STARS:
|
||||
case RATING_5_STARS:
|
||||
case RATING_PERCENTAGE:
|
||||
return new RatingCompat(ratingStyle, RATING_NOT_RATED);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Rating instance with a heart-based rating. Create and return a new Rating instance
|
||||
* with a rating style of {@link #RATING_HEART}, and a heart-based rating.
|
||||
*
|
||||
* @param hasHeart true for a "heart selected" rating, false for "heart unselected".
|
||||
* @return a new Rating instance.
|
||||
*/
|
||||
public static RatingCompat newHeartRating(boolean hasHeart) {
|
||||
return new RatingCompat(RATING_HEART, hasHeart ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Rating instance with a thumb-based rating. Create and return a new Rating instance
|
||||
* with a {@link #RATING_THUMB_UP_DOWN} rating style, and a "thumb up" or "thumb down" rating.
|
||||
*
|
||||
* @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
|
||||
* @return a new Rating instance.
|
||||
*/
|
||||
public static RatingCompat newThumbRating(boolean thumbIsUp) {
|
||||
return new RatingCompat(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Rating instance with a star-based rating. Create and return a new Rating instance with
|
||||
* one of the star-base rating styles and the given integer or fractional number of stars. Non
|
||||
* integer values can for instance be used to represent an average rating value, which might not
|
||||
* be an integer number of stars.
|
||||
*
|
||||
* @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link
|
||||
* #RATING_5_STARS}.
|
||||
* @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to the rating
|
||||
* style.
|
||||
* @return null if the rating style is invalid, or the rating is out of range, a new Rating
|
||||
* instance otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public static RatingCompat newStarRating(@StarStyle int starRatingStyle, float starRating) {
|
||||
float maxRating = -1.0f;
|
||||
switch (starRatingStyle) {
|
||||
case RATING_3_STARS:
|
||||
maxRating = 3.0f;
|
||||
break;
|
||||
case RATING_4_STARS:
|
||||
maxRating = 4.0f;
|
||||
break;
|
||||
case RATING_5_STARS:
|
||||
maxRating = 5.0f;
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
|
||||
return null;
|
||||
}
|
||||
if ((starRating < 0.0f) || (starRating > maxRating)) {
|
||||
Log.e(TAG, "Trying to set out of range star-based rating");
|
||||
return null;
|
||||
}
|
||||
return new RatingCompat(starRatingStyle, starRating);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Rating instance with a percentage-based rating. Create and return a new Rating
|
||||
* instance with a {@link #RATING_PERCENTAGE} rating style, and a rating of the given percentage.
|
||||
*
|
||||
* @param percent the value of the rating
|
||||
* @return null if the rating is out of range, a new Rating instance otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public static RatingCompat newPercentageRating(float percent) {
|
||||
if ((percent < 0.0f) || (percent > 100.0f)) {
|
||||
Log.e(TAG, "Invalid percentage-based rating value");
|
||||
return null;
|
||||
} else {
|
||||
return new RatingCompat(RATING_PERCENTAGE, percent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether there is a rating value available.
|
||||
*
|
||||
* @return true if the instance was not created with {@link #newUnratedRating(int)}.
|
||||
*/
|
||||
public boolean isRated() {
|
||||
return mRatingValue >= 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rating style.
|
||||
*
|
||||
* @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS},
|
||||
* {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, or {@link #RATING_PERCENTAGE}.
|
||||
*/
|
||||
@Style
|
||||
public int getRatingStyle() {
|
||||
return mRatingStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rating is "heart selected".
|
||||
*
|
||||
* @return true if the rating is "heart selected", false if the rating is "heart unselected", if
|
||||
* the rating style is not {@link #RATING_HEART} or if it is unrated.
|
||||
*/
|
||||
public boolean hasHeart() {
|
||||
if (mRatingStyle != RATING_HEART) {
|
||||
return false;
|
||||
} else {
|
||||
return (mRatingValue == 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the rating is "thumb up".
|
||||
*
|
||||
* @return true if the rating is "thumb up", false if the rating is "thumb down", if the rating
|
||||
* style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
|
||||
*/
|
||||
public boolean isThumbUp() {
|
||||
if (mRatingStyle != RATING_THUMB_UP_DOWN) {
|
||||
return false;
|
||||
} else {
|
||||
return (mRatingValue == 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the star-based rating value.
|
||||
*
|
||||
* @return a rating value greater or equal to 0.0f, or a negative value if the rating style is not
|
||||
* star-based, or if it is unrated.
|
||||
*/
|
||||
public float getStarRating() {
|
||||
switch (mRatingStyle) {
|
||||
case RATING_3_STARS:
|
||||
case RATING_4_STARS:
|
||||
case RATING_5_STARS:
|
||||
if (isRated()) {
|
||||
return mRatingValue;
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
return -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the percentage-based rating value.
|
||||
*
|
||||
* @return a rating value greater or equal to 0.0f, or a negative value if the rating style is not
|
||||
* percentage-based, or if it is unrated.
|
||||
*/
|
||||
public float getPercentRating() {
|
||||
if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
|
||||
return -1.0f;
|
||||
} else {
|
||||
return mRatingValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from a framework {@link android.media.Rating} object.
|
||||
*
|
||||
* <p>This method is only supported on API 19+.
|
||||
*
|
||||
* @param ratingObj A {@link android.media.Rating} object, or null if none.
|
||||
* @return An equivalent {@link RatingCompat} object, or null if none.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressLint("WrongConstant")
|
||||
public static RatingCompat fromRating(@Nullable Object ratingObj) {
|
||||
if (ratingObj != null) {
|
||||
final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
|
||||
final RatingCompat rating;
|
||||
if (((Rating) ratingObj).isRated()) {
|
||||
switch (ratingStyle) {
|
||||
case Rating.RATING_HEART:
|
||||
rating = newHeartRating(((Rating) ratingObj).hasHeart());
|
||||
break;
|
||||
case Rating.RATING_THUMB_UP_DOWN:
|
||||
rating = newThumbRating(((Rating) ratingObj).isThumbUp());
|
||||
break;
|
||||
case Rating.RATING_3_STARS:
|
||||
case Rating.RATING_4_STARS:
|
||||
case Rating.RATING_5_STARS:
|
||||
rating = newStarRating(ratingStyle, ((Rating) ratingObj).getStarRating());
|
||||
break;
|
||||
case Rating.RATING_PERCENTAGE:
|
||||
rating = newPercentageRating(((Rating) ratingObj).getPercentRating());
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
rating = newUnratedRating(ratingStyle);
|
||||
}
|
||||
checkNotNull(rating).mRatingObj = ratingObj;
|
||||
return rating;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying framework {@link android.media.Rating} object.
|
||||
*
|
||||
* <p>This method is only supported on API 19+.
|
||||
*
|
||||
* @return An equivalent {@link android.media.Rating} object, or null if none.
|
||||
*/
|
||||
@Nullable
|
||||
public Object getRating() {
|
||||
if (mRatingObj == null) {
|
||||
if (isRated()) {
|
||||
switch (mRatingStyle) {
|
||||
case RATING_HEART:
|
||||
mRatingObj = Rating.newHeartRating(hasHeart());
|
||||
break;
|
||||
case RATING_THUMB_UP_DOWN:
|
||||
mRatingObj = Rating.newThumbRating(isThumbUp());
|
||||
break;
|
||||
case RATING_3_STARS:
|
||||
case RATING_4_STARS:
|
||||
case RATING_5_STARS:
|
||||
mRatingObj = Rating.newStarRating(mRatingStyle, getStarRating());
|
||||
break;
|
||||
case RATING_PERCENTAGE:
|
||||
mRatingObj = Rating.newPercentageRating(getPercentRating());
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
mRatingObj = Rating.newUnratedRating(mRatingStyle);
|
||||
}
|
||||
}
|
||||
return mRatingObj;
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.media.VolumeProvider;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.DoNotInline;
|
||||
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;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Handles requests to adjust or set the volume on a session. This is also used to push volume
|
||||
* updates back to the session after a request has been handled. You can set a volume provider on a
|
||||
* session by calling {@link MediaSessionCompat#setPlaybackToRemote}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract class VolumeProviderCompat {
|
||||
|
||||
/** */
|
||||
@IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ControlType {}
|
||||
|
||||
/** The volume is fixed and can not be modified. Requests to change volume should be ignored. */
|
||||
public static final int VOLUME_CONTROL_FIXED = 0;
|
||||
|
||||
/**
|
||||
* The volume control uses relative adjustment via {@link #onAdjustVolume(int)}. Attempts to set
|
||||
* the volume to a specific value should be ignored.
|
||||
*/
|
||||
public static final int VOLUME_CONTROL_RELATIVE = 1;
|
||||
|
||||
/**
|
||||
* The volume control uses an absolute value. It may be adjusted using {@link
|
||||
* #onAdjustVolume(int)} or set directly using {@link #onSetVolumeTo(int)}.
|
||||
*/
|
||||
public static final int VOLUME_CONTROL_ABSOLUTE = 2;
|
||||
|
||||
private final int mControlType;
|
||||
private final int mMaxVolume;
|
||||
@Nullable private final String mControlId;
|
||||
private int mCurrentVolume;
|
||||
@Nullable private Callback mCallback;
|
||||
|
||||
@Nullable private VolumeProvider mVolumeProviderFwk;
|
||||
|
||||
/**
|
||||
* Create a new volume provider for handling volume events. You must specify the type of volume
|
||||
* control and the maximum volume that can be used.
|
||||
*
|
||||
* @param volumeControl The method for controlling volume that is used by this provider.
|
||||
* @param maxVolume The maximum allowed volume.
|
||||
* @param currentVolume The current volume.
|
||||
*/
|
||||
public VolumeProviderCompat(@ControlType int volumeControl, int maxVolume, int currentVolume) {
|
||||
this(volumeControl, maxVolume, currentVolume, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new volume provider for handling volume events. You must specify the type of volume
|
||||
* control and the maximum volume that can be used.
|
||||
*
|
||||
* @param volumeControl The method for controlling volume that is used by this provider.
|
||||
* @param maxVolume The maximum allowed volume.
|
||||
* @param currentVolume The current volume.
|
||||
* @param volumeControlId The volume control ID of this provider.
|
||||
*/
|
||||
public VolumeProviderCompat(
|
||||
@ControlType int volumeControl,
|
||||
int maxVolume,
|
||||
int currentVolume,
|
||||
@Nullable String volumeControlId) {
|
||||
mControlType = volumeControl;
|
||||
mMaxVolume = maxVolume;
|
||||
mCurrentVolume = currentVolume;
|
||||
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.
|
||||
*
|
||||
* @return The max allowed volume.
|
||||
*/
|
||||
public final int getMaxVolume() {
|
||||
return mMaxVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current volume and notify the system that the volume has been changed.
|
||||
*
|
||||
* @param currentVolume The current volume of the output.
|
||||
*/
|
||||
public final void setCurrentVolume(int currentVolume) {
|
||||
mCurrentVolume = currentVolume;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
VolumeProvider volumeProviderFwk = (VolumeProvider) getVolumeProvider();
|
||||
Api21Impl.setCurrentVolume(volumeProviderFwk, 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.
|
||||
*
|
||||
* @param volume The volume to set the output to.
|
||||
*/
|
||||
public void onSetVolumeTo(int volume) {}
|
||||
|
||||
/**
|
||||
* Override to handle requests to adjust the volume of the current output.
|
||||
*
|
||||
* @param direction The direction to adjust the volume in.
|
||||
*/
|
||||
public void onAdjustVolume(int direction) {}
|
||||
|
||||
/**
|
||||
* Sets a callback to receive volume changes.
|
||||
*
|
||||
* <p>Used internally by the support library.
|
||||
*/
|
||||
public void setCallback(@Nullable Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying framework {@link android.media.VolumeProvider} object.
|
||||
*
|
||||
* <p>This method is only supported on API 21+.
|
||||
*
|
||||
* @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) {
|
||||
mVolumeProviderFwk =
|
||||
new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume, mControlId) {
|
||||
@Override
|
||||
public void onSetVolumeTo(int volume) {
|
||||
VolumeProviderCompat.this.onSetVolumeTo(volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdjustVolume(int direction) {
|
||||
VolumeProviderCompat.this.onAdjustVolume(direction);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
mVolumeProviderFwk =
|
||||
new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
|
||||
@Override
|
||||
public void onSetVolumeTo(int volume) {
|
||||
VolumeProviderCompat.this.onSetVolumeTo(volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdjustVolume(int direction) {
|
||||
VolumeProviderCompat.this.onAdjustVolume(direction);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return mVolumeProviderFwk;
|
||||
}
|
||||
|
||||
/** Listens for changes to the volume. */
|
||||
public abstract static class Callback {
|
||||
public abstract void onVolumeChanged(VolumeProviderCompat volumeProvider);
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static class Api21Impl {
|
||||
private Api21Impl() {}
|
||||
|
||||
@DoNotInline
|
||||
static void setCurrentVolume(VolumeProvider volumeProvider, int currentVolume) {
|
||||
volumeProvider.setCurrentVolume(currentVolume);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@NonNullApi
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:id="@+id/action0"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"/>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:id="@+id/cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_weight="1"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"/>
|
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
>
|
||||
<include layout="@layout/notification_template_icon_group"
|
||||
android:layout_width="@dimen/notification_large_icon_width"
|
||||
android:layout_height="@dimen/notification_large_icon_height"
|
||||
/>
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true" />
|
||||
<include layout="@layout/notification_template_lines_media"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="fill_vertical"
|
||||
android:layout_marginLeft="@dimen/notification_large_icon_width"
|
||||
android:layout_marginStart="@dimen/notification_large_icon_width"
|
||||
android:layout_toLeftOf="@id/cancel_action"
|
||||
android:layout_toStartOf="@id/cancel_action"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_above="@id/media_actions"
|
||||
android:id="@+id/action_divider"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
</RelativeLayout>
|
@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
>
|
||||
<include layout="@layout/notification_template_icon_group"
|
||||
android:layout_width="@dimen/notification_large_icon_width"
|
||||
android:layout_height="@dimen/notification_large_icon_height"
|
||||
/>
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/notification_main_column_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/notification_large_icon_height"
|
||||
android:layout_marginStart="@dimen/notification_large_icon_height"
|
||||
android:minHeight="@dimen/notification_large_icon_height"
|
||||
android:paddingTop="@dimen/notification_main_column_padding_top"
|
||||
android:orientation="horizontal"
|
||||
android:layout_toLeftOf="@id/cancel_action"
|
||||
android:layout_toStartOf="@id/cancel_action">
|
||||
<FrameLayout
|
||||
android:id="@+id/notification_main_column"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="@dimen/notification_content_margin_start"
|
||||
android:layout_marginStart="@dimen/notification_content_margin_start"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
/>
|
||||
<FrameLayout
|
||||
android:id="@+id/right_side"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="@dimen/notification_right_side_padding_top">
|
||||
<DateTimeView android:id="@+id/time"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<Chronometer android:id="@+id/chronometer"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<TextView android:id="@+id/info"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Info.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_above="@id/media_actions"
|
||||
android:id="@+id/action_divider"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
</RelativeLayout>
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
>
|
||||
<ImageView android:id="@+id/icon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<include layout="@layout/notification_template_lines_media"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="128dp"
|
||||
android:layout_marginStart="128dp"
|
||||
android:layout_toLeftOf="@id/cancel_action"
|
||||
android:layout_toStartOf="@id/cancel_action"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_above="@id/media_actions"
|
||||
android:id="@+id/action_divider"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
</RelativeLayout>
|
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
>
|
||||
<ImageView android:id="@+id/icon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/notification_main_column_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="128dp"
|
||||
android:layout_marginStart="128dp"
|
||||
android:minHeight="@dimen/notification_large_icon_height"
|
||||
android:paddingTop="@dimen/notification_main_column_padding_top"
|
||||
android:orientation="horizontal"
|
||||
android:layout_toLeftOf="@id/cancel_action"
|
||||
android:layout_toStartOf="@id/cancel_action">
|
||||
<FrameLayout
|
||||
android:id="@+id/notification_main_column"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="@dimen/notification_media_narrow_margin"
|
||||
android:layout_marginStart="@dimen/notification_media_narrow_margin"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/right_side"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="@dimen/notification_right_side_padding_top">
|
||||
<DateTimeView android:id="@+id/time"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<Chronometer android:id="@+id/chronometer"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<TextView android:id="@+id/info"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Info.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:layout_above="@id/media_actions"
|
||||
android:id="@+id/action_divider"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
</RelativeLayout>
|
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
>
|
||||
<LinearLayout
|
||||
android:id="@+id/line1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dp"
|
||||
android:layout_marginLeft="@dimen/notification_content_margin_start"
|
||||
android:layout_marginStart="@dimen/notification_content_margin_start"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<TextView android:id="@+id/title"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Title.Media"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
<DateTimeView android:id="@+id/time"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:visibility="gone"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingStart="8dp"
|
||||
/>
|
||||
<Chronometer android:id="@+id/chronometer"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:visibility="gone"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingStart="8dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/text2"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Line2.Media"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:layout_marginBottom="-2dp"
|
||||
android:layout_marginLeft="@dimen/notification_content_margin_start"
|
||||
android:layout_marginStart="@dimen/notification_content_margin_start"
|
||||
android:singleLine="true"
|
||||
android:fadingEdge="horizontal"
|
||||
android:ellipsize="marquee"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<LinearLayout
|
||||
android:id="@+id/line3"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/notification_content_margin_start"
|
||||
android:layout_marginStart="@dimen/notification_content_margin_start"
|
||||
>
|
||||
<TextView android:id="@+id/text"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Media"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
/>
|
||||
<TextView android:id="@+id/info"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Info.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:singleLine="true"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingStart="8dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<include layout="@layout/notification_template_icon_group"
|
||||
android:layout_width="@dimen/notification_large_icon_width"
|
||||
android:layout_height="@dimen/notification_large_icon_height"
|
||||
/>
|
||||
<include layout="@layout/notification_template_lines_media"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
<ImageView android:id="@+id/end_padder"
|
||||
android:layout_width="6dp"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</LinearLayout>
|
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/status_bar_latest_event_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<include layout="@layout/notification_template_icon_group"
|
||||
android:layout_width="@dimen/notification_large_icon_width"
|
||||
android:layout_height="@dimen/notification_large_icon_height"
|
||||
/>
|
||||
<LinearLayout
|
||||
android:id="@+id/notification_main_column_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/notification_main_column_padding_top"
|
||||
android:minHeight="@dimen/notification_large_icon_height"
|
||||
android:orientation="horizontal">
|
||||
<FrameLayout
|
||||
android:id="@+id/notification_main_column"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="@dimen/notification_content_margin_start"
|
||||
android:layout_marginStart="@dimen/notification_content_margin_start"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/right_side"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="@dimen/notification_right_side_padding_top">
|
||||
<DateTimeView android:id="@+id/time"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<Chronometer android:id="@+id/chronometer"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Time.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_gravity="end|top"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<TextView android:id="@+id/info"
|
||||
android:textAppearance="@style/TextAppearance.Compat.Notification.Info.Media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
>
|
||||
<!-- media buttons will be added here -->
|
||||
</LinearLayout>
|
||||
<include layout="@layout/notification_media_cancel_action"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
<ImageView android:id="@+id/end_padder"
|
||||
android:layout_width="6dp"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</LinearLayout>
|
33
libraries/session/src/main/res/values-v21/styles.xml
Normal file
33
libraries/session/src/main/res/values-v21/styles.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="TextAppearance.Compat.Notification.Media" >
|
||||
<item name="android:textColor">@color/secondary_text_default_material_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Title.Media" >
|
||||
<item name="android:textColor">@color/primary_text_default_material_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Info.Media">
|
||||
<item name="android:textColor">@color/secondary_text_default_material_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Time.Media">
|
||||
<item name="android:textColor">@color/secondary_text_default_material_dark</item>
|
||||
</style>
|
||||
</resources>
|
25
libraries/session/src/main/res/values-v24/styles.xml
Normal file
25
libraries/session/src/main/res/values-v24/styles.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Use platform styles, Media is dark again -->
|
||||
<style name="TextAppearance.Compat.Notification.Media" />
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Title.Media" />
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Info.Media" />
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Time.Media" />
|
||||
</resources>
|
20
libraries/session/src/main/res/values/colors.xml
Normal file
20
libraries/session/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<!-- The color of the material notification background for media notifications when no custom
|
||||
color is specified -->
|
||||
<color name="notification_material_background_media_default_color">#ff424242</color>
|
||||
</resources>
|
23
libraries/session/src/main/res/values/colors_material.xml
Normal file
23
libraries/session/src/main/res/values/colors_material.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Colors specific to Material themes. -->
|
||||
<resources>
|
||||
<!-- 100% white -->
|
||||
<color name="primary_text_default_material_dark">#ffffffff</color>
|
||||
<!-- 70% white -->
|
||||
<color name="secondary_text_default_material_dark">#b3ffffff</color>
|
||||
</resources>
|
21
libraries/session/src/main/res/values/config.xml
Normal file
21
libraries/session/src/main/res/values/config.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- These resources are around just to allow their values to be customized
|
||||
for different hardware and product builds. -->
|
||||
<resources>
|
||||
<integer name="cancel_button_image_alpha">127</integer>
|
||||
</resources>
|
19
libraries/session/src/main/res/values/ids.xml
Normal file
19
libraries/session/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<item type="id" name="media_controller_compat_view_tag" />
|
||||
</resources>
|
27
libraries/session/src/main/res/values/styles.xml
Normal file
27
libraries/session/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="TextAppearance.Compat.Notification.Media" />
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Title.Media" />
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Info.Media"/>
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Time.Media"/>
|
||||
|
||||
<style name="TextAppearance.Compat.Notification.Line2.Media" parent="TextAppearance.Compat.Notification.Info.Media"/>
|
||||
</resources>
|
@ -15,13 +15,13 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_BROWSABLE;
|
||||
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
||||
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
|
||||
import static androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT;
|
||||
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
|
||||
import static androidx.media3.session.legacy.MediaBrowserCompat.MediaItem.FLAG_BROWSABLE;
|
||||
import static androidx.media3.session.legacy.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
||||
import static androidx.media3.session.legacy.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
|
||||
import static androidx.media3.session.legacy.MediaMetadataCompat.METADATA_KEY_DURATION;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.fail;
|
||||
@ -32,17 +32,8 @@ import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.service.media.MediaBrowserService;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.RatingCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.text.SpannedString;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.AudioAttributesCompat;
|
||||
import androidx.media.VolumeProviderCompat;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.HeartRating;
|
||||
@ -55,6 +46,15 @@ import androidx.media3.common.StarRating;
|
||||
import androidx.media3.common.ThumbRating;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.session.legacy.AudioAttributesCompat;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||
import androidx.media3.session.legacy.MediaControllerCompat;
|
||||
import androidx.media3.session.legacy.MediaDescriptionCompat;
|
||||
import androidx.media3.session.legacy.MediaMetadataCompat;
|
||||
import androidx.media3.session.legacy.MediaSessionCompat;
|
||||
import androidx.media3.session.legacy.PlaybackStateCompat;
|
||||
import androidx.media3.session.legacy.RatingCompat;
|
||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
@ -19,8 +19,8 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.media.MediaSessionManager;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.session.legacy.MediaSessionManager;
|
||||
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.After;
|
||||
|
@ -495,6 +495,38 @@ public class MediaSessionTest {
|
||||
assertThat(player.seekPositionMs).isEqualTo(testSeekPositionMs);
|
||||
}
|
||||
|
||||
/** Test {@link MediaSession#getSessionCompatToken()}. */
|
||||
@Test
|
||||
public void getPlatformToken_returnsCompatibleWithPlatformMediaController() throws Exception {
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("getPlatformToken_returnsCompatibleWithPlatformMediaController")
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
if (TextUtils.equals(
|
||||
getControllerCallerPackageName(controller),
|
||||
controller.getPackageName())) {
|
||||
return MediaSession.Callback.super.onConnect(session, controller);
|
||||
}
|
||||
return MediaSession.ConnectionResult.reject();
|
||||
}
|
||||
})
|
||||
.build());
|
||||
android.media.session.MediaSession.Token token = session.getPlatformToken();
|
||||
android.media.session.MediaController platformController =
|
||||
new android.media.session.MediaController(context, token);
|
||||
|
||||
long testSeekPositionMs = 1234;
|
||||
platformController.getTransportControls().seekTo(testSeekPositionMs);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO, TIMEOUT_MS);
|
||||
assertThat(player.seekPositionMs).isEqualTo(testSeekPositionMs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getControllerVersion() throws Exception {
|
||||
CountDownLatch connectedLatch = new CountDownLatch(1);
|
||||
|
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
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;
|
||||
|
||||
/** 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,
|
||||
mMediaLegacyAAC,
|
||||
mMediaAACFromAA,
|
||||
mNotificationAAC,
|
||||
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);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMediaAAC =
|
||||
mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC, AudioAttributesCompat.USAGE_MEDIA)
|
||||
.build();
|
||||
mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
|
||||
mNotificationAAC =
|
||||
mkBuilder(
|
||||
AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
|
||||
AudioAttributesCompat.USAGE_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));
|
||||
assertThat(
|
||||
(AudioAttributes) mMediaAACFromAA.unwrap(),
|
||||
equalTo(new AudioAttributes.Builder((AudioAttributes) mMediaAA).build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 21)
|
||||
public void testEqualityApi21() {
|
||||
assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
|
||||
assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquality() {
|
||||
assertThat("self equality", mMediaAAC, equalTo(mMediaAAC));
|
||||
assertThat(
|
||||
"equal to clone", mMediaAAC, equalTo(new AudioAttributesCompat.Builder(mMediaAAC).build()));
|
||||
assertThat("different things are different", mMediaAAC, not(equalTo(mNotificationAAC)));
|
||||
assertThat("different things are different 2", mNotificationAAC, not(equalTo(mMediaAAC)));
|
||||
assertThat(
|
||||
"equal to clone 2",
|
||||
mNotificationAAC,
|
||||
equalTo(new AudioAttributesCompat.Builder(mNotificationAAC).build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetters() {
|
||||
assertThat(mMediaAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC));
|
||||
assertThat(mMediaAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA));
|
||||
assertThat(mMediaAAC.getFlags(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyStreamTypeInference() {
|
||||
assertThat(mMediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
|
||||
assertThat(mMediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
|
||||
assertThat(mNotificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
|
||||
assertThat(
|
||||
mNotificationLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
|
||||
}
|
||||
|
||||
@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();
|
||||
assertThat(alarmAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_ALARM));
|
||||
assertThat(alarmAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION));
|
||||
|
||||
AudioAttributesCompat musicAAC = mkBuilder(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();
|
||||
assertThat(notificationAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_NOTIFICATION));
|
||||
assertThat(
|
||||
notificationAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION));
|
||||
|
||||
AudioAttributesCompat voiceCallAAC = mkBuilder(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);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
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;
|
||||
|
||||
/** Tests for {@link MediaDescriptionCompat}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MediaDescriptionCompatTest {
|
||||
|
||||
@SdkSuppress(minSdkVersion = 21)
|
||||
@Test
|
||||
public void roundTripViaFrameworkObject_returnsEqualMediaUriAndExtras() {
|
||||
Uri mediaUri = Uri.parse("androidx://media/uri");
|
||||
MediaDescriptionCompat originalDescription =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setMediaUri(mediaUri)
|
||||
.setExtras(createExtras())
|
||||
.build();
|
||||
|
||||
MediaDescriptionCompat restoredDescription =
|
||||
MediaDescriptionCompat.fromMediaDescription(originalDescription.getMediaDescription());
|
||||
|
||||
// Test second round-trip as MediaDescriptionCompat keeps an internal reference to a previously
|
||||
// restored platform instance.
|
||||
MediaDescriptionCompat restoredDescription2 =
|
||||
MediaDescriptionCompat.fromMediaDescription(restoredDescription.getMediaDescription());
|
||||
|
||||
assertEquals(mediaUri, restoredDescription.getMediaUri());
|
||||
TestUtils.equals(createExtras(), restoredDescription.getExtras());
|
||||
assertEquals(mediaUri, restoredDescription2.getMediaUri());
|
||||
TestUtils.equals(createExtras(), restoredDescription2.getExtras());
|
||||
}
|
||||
|
||||
@SdkSuppress(minSdkVersion = 21)
|
||||
@Test
|
||||
public void getMediaDescription_withMediaUri_doesNotTouchExtras() {
|
||||
MediaDescriptionCompat originalDescription =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setMediaUri(Uri.EMPTY)
|
||||
.setExtras(createExtras())
|
||||
.build();
|
||||
originalDescription.getMediaDescription();
|
||||
TestUtils.equals(createExtras(), originalDescription.getExtras());
|
||||
}
|
||||
|
||||
private static Bundle createExtras() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString("key1", "value1");
|
||||
extras.putString("key2", "value2");
|
||||
return extras;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Parcel;
|
||||
import androidx.media3.session.legacy.MediaBrowserCompat.MediaItem;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Test {@link MediaItem}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MediaItemTest {
|
||||
private static final String DESCRIPTION = "test_description";
|
||||
private static final String MEDIA_ID = "test_media_id";
|
||||
private static final String TITLE = "test_title";
|
||||
private static final String SUBTITLE = "test_subtitle";
|
||||
|
||||
@Test
|
||||
public void testBrowsableMediaItem() {
|
||||
MediaDescriptionCompat description =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setDescription(DESCRIPTION)
|
||||
.setMediaId(MEDIA_ID)
|
||||
.setTitle(TITLE)
|
||||
.setSubtitle(SUBTITLE)
|
||||
.build();
|
||||
MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
|
||||
|
||||
assertEquals(description.toString(), mediaItem.getDescription().toString());
|
||||
assertEquals(MEDIA_ID, mediaItem.getMediaId());
|
||||
assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
|
||||
assertTrue(mediaItem.isBrowsable());
|
||||
assertFalse(mediaItem.isPlayable());
|
||||
assertEquals(0, mediaItem.describeContents());
|
||||
|
||||
// Test writeToParcel
|
||||
Parcel p = Parcel.obtain();
|
||||
mediaItem.writeToParcel(p, 0);
|
||||
p.setDataPosition(0);
|
||||
assertEquals(mediaItem.getFlags(), p.readInt());
|
||||
assertEquals(
|
||||
description.toString(), MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
|
||||
p.recycle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayableMediaItem() {
|
||||
MediaDescriptionCompat description =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setDescription(DESCRIPTION)
|
||||
.setMediaId(MEDIA_ID)
|
||||
.setTitle(TITLE)
|
||||
.setSubtitle(SUBTITLE)
|
||||
.build();
|
||||
MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
|
||||
|
||||
assertEquals(description.toString(), mediaItem.getDescription().toString());
|
||||
assertEquals(MEDIA_ID, mediaItem.getMediaId());
|
||||
assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
|
||||
assertFalse(mediaItem.isBrowsable());
|
||||
assertTrue(mediaItem.isPlayable());
|
||||
assertEquals(0, mediaItem.describeContents());
|
||||
|
||||
// Test writeToParcel
|
||||
Parcel p = Parcel.obtain();
|
||||
mediaItem.writeToParcel(p, 0);
|
||||
p.setDataPosition(0);
|
||||
assertEquals(mediaItem.getFlags(), p.readInt());
|
||||
assertEquals(
|
||||
description.toString(), MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
|
||||
p.recycle();
|
||||
}
|
||||
}
|
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.util.ArrayList;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Test {@link PlaybackStateCompat}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PlaybackStateCompatTest {
|
||||
|
||||
private static final long TEST_POSITION = 20000L;
|
||||
private static final long TEST_BUFFERED_POSITION = 15000L;
|
||||
private static final long TEST_UPDATE_TIME = 100000L;
|
||||
private static final long TEST_ACTIONS =
|
||||
PlaybackStateCompat.ACTION_PLAY
|
||||
| PlaybackStateCompat.ACTION_STOP
|
||||
| PlaybackStateCompat.ACTION_SEEK_TO;
|
||||
private static final long TEST_QUEUE_ITEM_ID = 23L;
|
||||
private static final float TEST_PLAYBACK_SPEED = 3.0f;
|
||||
private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
|
||||
private static final float DELTA = 1e-7f;
|
||||
|
||||
private static final int TEST_ERROR_CODE = PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED;
|
||||
private static final String TEST_ERROR_MSG = "test-error-msg";
|
||||
private static final String TEST_CUSTOM_ACTION = "test-custom-action";
|
||||
private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
|
||||
private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
|
||||
|
||||
private static final String EXTRAS_KEY = "test-key";
|
||||
private static final String EXTRAS_VALUE = "test-value";
|
||||
|
||||
/** Test default values of {@link PlaybackStateCompat}. */
|
||||
@Test
|
||||
public void testBuilder() {
|
||||
PlaybackStateCompat state = new PlaybackStateCompat.Builder().build();
|
||||
|
||||
assertEquals(new ArrayList<PlaybackStateCompat.CustomAction>(), state.getCustomActions());
|
||||
assertEquals(0, state.getState());
|
||||
assertEquals(0L, state.getPosition());
|
||||
assertEquals(0L, state.getBufferedPosition());
|
||||
assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
|
||||
assertEquals(0L, state.getActions());
|
||||
assertEquals(0, state.getErrorCode());
|
||||
assertNull(state.getErrorMessage());
|
||||
assertEquals(0L, state.getLastPositionUpdateTime());
|
||||
assertEquals(MediaSessionCompat.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
|
||||
assertNull(state.getExtras());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test following setter methods of {@link PlaybackStateCompat.Builder}: {@link
|
||||
* PlaybackStateCompat.Builder#setState(int, long, float)} {@link
|
||||
* PlaybackStateCompat.Builder#setActions(long)} {@link
|
||||
* PlaybackStateCompat.Builder#setActiveQueueItemId(long)} {@link
|
||||
* PlaybackStateCompat.Builder#setBufferedPosition(long)} {@link
|
||||
* PlaybackStateCompat.Builder#setErrorMessage(CharSequence)} {@link
|
||||
* PlaybackStateCompat.Builder#setExtras(Bundle)}
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testBuilder_setterMethods() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
||||
|
||||
PlaybackStateCompat state =
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
|
||||
.setActions(TEST_ACTIONS)
|
||||
.setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
|
||||
.setBufferedPosition(TEST_BUFFERED_POSITION)
|
||||
.setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
|
||||
.setExtras(extras)
|
||||
.build();
|
||||
assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState());
|
||||
assertEquals(TEST_POSITION, state.getPosition());
|
||||
assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
|
||||
assertEquals(TEST_ACTIONS, state.getActions());
|
||||
assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
|
||||
assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
|
||||
assertEquals(TEST_ERROR_CODE, state.getErrorCode());
|
||||
assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
|
||||
assertNotNull(state.getExtras());
|
||||
assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat.Builder#setState(int, long, float, long)}. */
|
||||
@Test
|
||||
public void testBuilder_setStateWithUpdateTime() {
|
||||
PlaybackStateCompat state =
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(
|
||||
PlaybackStateCompat.STATE_REWINDING,
|
||||
TEST_POSITION,
|
||||
TEST_PLAYBACK_SPEED_ON_REWIND,
|
||||
TEST_UPDATE_TIME)
|
||||
.build();
|
||||
assertEquals(PlaybackStateCompat.STATE_REWINDING, state.getState());
|
||||
assertEquals(TEST_POSITION, state.getPosition());
|
||||
assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
|
||||
assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat.Builder#addCustomAction(String, String, int)}. */
|
||||
@Test
|
||||
public void testBuilder_addCustomAction() {
|
||||
ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
|
||||
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
actions.add(
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
|
||||
.build());
|
||||
builder.addCustomAction(
|
||||
TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
|
||||
}
|
||||
|
||||
PlaybackStateCompat state = builder.build();
|
||||
assertEquals(actions.size(), state.getCustomActions().size());
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}. */
|
||||
@Test
|
||||
public void testBuilder_addCustomActionWithCustomActionObject() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
||||
|
||||
ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
|
||||
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
actions.add(
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
|
||||
.setExtras(extras)
|
||||
.build());
|
||||
builder.addCustomAction(
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
|
||||
.setExtras(extras)
|
||||
.build());
|
||||
}
|
||||
|
||||
PlaybackStateCompat state = builder.build();
|
||||
assertEquals(actions.size(), state.getCustomActions().size());
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat#writeToParcel(Parcel, int)}. */
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testWriteToParcel() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
||||
|
||||
PlaybackStateCompat.Builder builder =
|
||||
new PlaybackStateCompat.Builder()
|
||||
.setState(
|
||||
PlaybackStateCompat.STATE_CONNECTING,
|
||||
TEST_POSITION,
|
||||
TEST_PLAYBACK_SPEED,
|
||||
TEST_UPDATE_TIME)
|
||||
.setActions(TEST_ACTIONS)
|
||||
.setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
|
||||
.setBufferedPosition(TEST_BUFFERED_POSITION)
|
||||
.setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
|
||||
.setExtras(extras);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
builder.addCustomAction(
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
|
||||
.setExtras(extras)
|
||||
.build());
|
||||
}
|
||||
PlaybackStateCompat state = builder.build();
|
||||
|
||||
Parcel parcel = Parcel.obtain();
|
||||
state.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
PlaybackStateCompat stateOut = PlaybackStateCompat.CREATOR.createFromParcel(parcel);
|
||||
assertEquals(PlaybackStateCompat.STATE_CONNECTING, stateOut.getState());
|
||||
assertEquals(TEST_POSITION, stateOut.getPosition());
|
||||
assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
|
||||
assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
|
||||
assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
|
||||
assertEquals(TEST_ACTIONS, stateOut.getActions());
|
||||
assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
|
||||
assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
|
||||
assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
|
||||
assertNotNull(stateOut.getExtras());
|
||||
assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
|
||||
|
||||
assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
|
||||
for (int i = 0; i < state.getCustomActions().size(); i++) {
|
||||
assertCustomActionEquals(state.getCustomActions().get(i), stateOut.getCustomActions().get(i));
|
||||
}
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat#describeContents()}. */
|
||||
@Test
|
||||
public void testDescribeContents() {
|
||||
assertEquals(0, new PlaybackStateCompat.Builder().build().describeContents());
|
||||
}
|
||||
|
||||
/** Test {@link PlaybackStateCompat.CustomAction}. */
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testCustomAction() {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
||||
|
||||
// Test Builder/Getters
|
||||
PlaybackStateCompat.CustomAction customAction =
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
|
||||
.setExtras(extras)
|
||||
.build();
|
||||
assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
|
||||
assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
|
||||
assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
|
||||
assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
|
||||
|
||||
// Test describeContents
|
||||
assertEquals(0, customAction.describeContents());
|
||||
|
||||
// Test writeToParcel
|
||||
Parcel parcel = Parcel.obtain();
|
||||
customAction.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
assertCustomActionEquals(
|
||||
customAction, PlaybackStateCompat.CustomAction.CREATOR.createFromParcel(parcel));
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
/** Tests that each ACTION_* constant does not overlap. */
|
||||
@Test
|
||||
public void testActionConstantDoesNotOverlap() {
|
||||
long[] actionConstants =
|
||||
new long[] {
|
||||
PlaybackStateCompat.ACTION_STOP,
|
||||
PlaybackStateCompat.ACTION_PAUSE,
|
||||
PlaybackStateCompat.ACTION_PLAY,
|
||||
PlaybackStateCompat.ACTION_REWIND,
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS,
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT,
|
||||
PlaybackStateCompat.ACTION_FAST_FORWARD,
|
||||
PlaybackStateCompat.ACTION_SET_RATING,
|
||||
PlaybackStateCompat.ACTION_SEEK_TO,
|
||||
PlaybackStateCompat.ACTION_PLAY_PAUSE,
|
||||
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID,
|
||||
PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH,
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM,
|
||||
PlaybackStateCompat.ACTION_PLAY_FROM_URI,
|
||||
PlaybackStateCompat.ACTION_PREPARE,
|
||||
PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID,
|
||||
PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH,
|
||||
PlaybackStateCompat.ACTION_PREPARE_FROM_URI,
|
||||
PlaybackStateCompat.ACTION_SET_REPEAT_MODE,
|
||||
PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE,
|
||||
PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED,
|
||||
PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED
|
||||
};
|
||||
|
||||
// Check that the values are not overlapped.
|
||||
for (int i = 0; i < actionConstants.length; i++) {
|
||||
for (int j = i + 1; j < actionConstants.length; j++) {
|
||||
assertEquals(0, actionConstants[i] & actionConstants[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void assertCustomActionEquals(
|
||||
PlaybackStateCompat.CustomAction action1, PlaybackStateCompat.CustomAction action2) {
|
||||
assertEquals(action1.getAction(), action2.getAction());
|
||||
assertEquals(action1.getName(), action2.getName());
|
||||
assertEquals(action1.getIcon(), action2.getIcon());
|
||||
|
||||
// To be the same, two extras should be both null or both not null.
|
||||
assertEquals(action1.getExtras() != null, action2.getExtras() != null);
|
||||
if (action1.getExtras() != null) {
|
||||
assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session.legacy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Test of {@link MediaSessionManager.RemoteUserInfo} methods. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RemoteUserInfoTest {
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
String testPackageName = "com.media.test";
|
||||
int testPid = 1000;
|
||||
int testUid = 2000;
|
||||
MediaSessionManager.RemoteUserInfo remoteUserInfo =
|
||||
new MediaSessionManager.RemoteUserInfo(testPackageName, testPid, testUid);
|
||||
assertEquals(testPackageName, remoteUserInfo.getPackageName());
|
||||
assertEquals(testPid, remoteUserInfo.getPid());
|
||||
assertEquals(testUid, remoteUserInfo.getUid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor_withNullPackageName_throwsNPE() {
|
||||
try {
|
||||
MediaSessionManager.RemoteUserInfo remoteUserInfo =
|
||||
new MediaSessionManager.RemoteUserInfo(null, 1000, 2000);
|
||||
fail("null package name shouldn't be allowed");
|
||||
} catch (NullPointerException e) {
|
||||
// expected
|
||||
} catch (Exception e) {
|
||||
fail("unexpected exception " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor_withEmptyPackageName_throwsIAE() {
|
||||
try {
|
||||
MediaSessionManager.RemoteUserInfo remoteUserInfo =
|
||||
new MediaSessionManager.RemoteUserInfo("", 1000, 2000);
|
||||
fail("empty package name shouldn't be allowed");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
} catch (Exception e) {
|
||||
fail("unexpected exception " + e);
|
||||
}
|
||||
}
|
||||
}
|
@ -241,8 +241,19 @@ public final class MediaTestUtils {
|
||||
List<MediaSessionCompat.QueueItem> list = new ArrayList<>();
|
||||
for (int i = 0; i < mediaItems.size(); i++) {
|
||||
MediaItem item = mediaItems.get(i);
|
||||
MediaDescriptionCompat description =
|
||||
androidx.media3.session.legacy.MediaDescriptionCompat media3Description =
|
||||
LegacyConversions.convertToMediaDescriptionCompat(item, null);
|
||||
MediaDescriptionCompat description =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setTitle(media3Description.getTitle())
|
||||
.setSubtitle(media3Description.getSubtitle())
|
||||
.setDescription(media3Description.getDescription())
|
||||
.setIconUri(media3Description.getIconUri())
|
||||
.setIconBitmap(media3Description.getIconBitmap())
|
||||
.setMediaId(media3Description.getMediaId())
|
||||
.setMediaUri(media3Description.getMediaUri())
|
||||
.setExtras(media3Description.getExtras())
|
||||
.build();
|
||||
long id = LegacyConversions.convertToQueueItemId(i);
|
||||
list.add(new MediaSessionCompat.QueueItem(description, id));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user